Split flying edges and marching cells into separate filters

In order to compile the contour filter more efficiently, we split the contour filter into two separate translation units, corresponding to the new filters ContourFlyingEdges and ContourMarchingCells. The API for Contour filter is left totally unchanged, and tries to use flying edges if the dataset is structured and uniform.
All three contour filters inherit from the `AbstractContour` class, providing utility methods used in the implementations.
This commit is contained in:
Louis Gombert 2023-04-25 10:54:35 +02:00
parent 13a68cc86b
commit dcdda3065a
17 changed files with 758 additions and 301 deletions

@ -438,8 +438,7 @@ void BenchContour(::benchmark::State& state)
filter.SetMergeDuplicatePoints(mergePoints);
filter.SetGenerateNormals(normals);
filter.SetComputeFastNormalsForStructured(fastNormals);
filter.SetComputeFastNormalsForUnstructured(fastNormals);
filter.SetComputeFastNormals(fastNormals);
vtkm::cont::Timer timer{ device };

@ -340,8 +340,7 @@ void BenchContour(::benchmark::State& state)
filter.SetActiveField(PointScalarsName, vtkm::cont::Field::Association::Points);
filter.SetMergeDuplicatePoints(true);
filter.SetGenerateNormals(true);
filter.SetComputeFastNormalsForStructured(true);
filter.SetComputeFastNormalsForUnstructured(true);
filter.SetComputeFastNormals(true);
state.ResumeTiming(); // And resume timers.
filterTimer.Start();

@ -0,0 +1,11 @@
# Split flying edges and marching cells into separate filters
The contour filter contains 2 separate implementations, Marching Cells and Flying Edges, the latter only available if the input has a `CellSetStructured<3>` and `ArrayHandleUniformPointCoordinates` for point coordinates. The compilation of this filter was lenghty and resource-heavy, because both algorithms were part of the same translation unit.
Now, this filter is separated into two new filters, `ContourFlyingEdges` and `ContourMarchingCells`, compiling more efficiently into two translation units. The `Contour` API is left unchanged. All 3 filters `Contour`, `ContourFlyingEdges` and `ContourMarchingCells` rely on a new abstract class `AbstractContour` to provide configuration and common utility functions.
Although `Contour` is still the preferred option for most cases because it selects the best implementation according to the input, `ContourMarchingCells` is usable on any kind of 3D Dataset. For now, `ContourFlyingEdges` operates only on structured uniform datasets.
Deprecate functions `GetComputeFastNormalsForStructured`, `SetComputeFastNormalsForStructured`, `GetComputeFastNormalsForUnstructured` and `GetComputeFastNormalsForUnstructured`, to use the more general `GetComputeFastNormals` and `SetComputeFastNormals` instead.
By default, for the `Contour` filter, `GenerateNormals` is now `true`, and `ComputeFastNormals` is `false`.

@ -0,0 +1,181 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#ifndef vtk_m_filter_contour_AbstractContour_h
#define vtk_m_filter_contour_AbstractContour_h
#include <vtkm/filter/FilterField.h>
#include <vtkm/filter/MapFieldPermutation.h>
#include <vtkm/filter/contour/vtkm_filter_contour_export.h>
#include <vtkm/filter/vector_analysis/SurfaceNormals.h>
namespace vtkm
{
namespace filter
{
namespace contour
{
/// \brief Contour filter interface
///
/// Provides common configuration & execution methods for contour filters
/// Only the method \c DoExecute executing the contour algorithm needs to be implemented
class VTKM_FILTER_CONTOUR_EXPORT AbstractContour : public vtkm::filter::FilterField
{
public:
void SetNumberOfIsoValues(vtkm::Id num)
{
if (num >= 0)
{
this->IsoValues.resize(static_cast<std::size_t>(num));
}
}
vtkm::Id GetNumberOfIsoValues() const { return static_cast<vtkm::Id>(this->IsoValues.size()); }
void SetIsoValue(vtkm::Float64 v) { this->SetIsoValue(0, v); }
void SetIsoValue(vtkm::Id index, vtkm::Float64 v)
{
std::size_t i = static_cast<std::size_t>(index);
if (i >= this->IsoValues.size())
{
this->IsoValues.resize(i + 1);
}
this->IsoValues[i] = v;
}
void SetIsoValues(const std::vector<vtkm::Float64>& values) { this->IsoValues = values; }
vtkm::Float64 GetIsoValue(vtkm::Id index) const
{
return this->IsoValues[static_cast<std::size_t>(index)];
}
/// Set/Get whether normals should be generated. Off by default.
VTKM_CONT
void SetGenerateNormals(bool on) { this->GenerateNormals = on; }
VTKM_CONT
bool GetGenerateNormals() const { return this->GenerateNormals; }
/// Set/Get whether to append the ids of the intersected edges to the vertices of the isosurface triangles. Off by default.
VTKM_CONT
void SetAddInterpolationEdgeIds(bool on) { this->AddInterpolationEdgeIds = on; }
VTKM_CONT
bool GetAddInterpolationEdgeIds() const { return this->AddInterpolationEdgeIds; }
/// Set/Get whether the fast path should be used for normals computation. Off by default.
VTKM_CONT
void SetComputeFastNormals(bool on) { this->ComputeFastNormals = on; }
VTKM_CONT
bool GetComputeFastNormals() const { return this->ComputeFastNormals; }
VTKM_CONT
void SetNormalArrayName(const std::string& name) { this->NormalArrayName = name; }
VTKM_CONT
const std::string& GetNormalArrayName() const { return this->NormalArrayName; }
/// Set/Get whether the points generated should be unique for every triangle
/// or will duplicate points be merged together. Duplicate points are identified
/// by the unique edge it was generated from.
///
VTKM_CONT
void SetMergeDuplicatePoints(bool on) { this->MergeDuplicatedPoints = on; }
VTKM_CONT
bool GetMergeDuplicatePoints() { return this->MergeDuplicatedPoints; }
protected:
/// \brief Map a given field to the output \c DataSet , depending on its type.
///
/// The worklet needs to implement \c ProcessPointField to process point fields as arrays
/// and \c GetCellIdMap function giving the cell id mapping from input to output
template <typename WorkletType>
VTKM_CONT static bool DoMapField(vtkm::cont::DataSet& result,
const vtkm::cont::Field& field,
WorkletType& worklet)
{
if (field.IsPointField())
{
vtkm::cont::UnknownArrayHandle inputArray = field.GetData();
vtkm::cont::UnknownArrayHandle outputArray = inputArray.NewInstanceBasic();
auto functor = [&](const auto& concrete) {
using ComponentType = typename std::decay_t<decltype(concrete)>::ValueType::ComponentType;
auto fieldArray = outputArray.ExtractArrayFromComponents<ComponentType>();
worklet.ProcessPointField(concrete, fieldArray);
};
inputArray.CastAndCallWithExtractedArray(functor);
result.AddPointField(field.GetName(), outputArray);
return true;
}
else if (field.IsCellField())
{
// Use the precompiled field permutation function.
vtkm::cont::ArrayHandle<vtkm::Id> permutation = worklet.GetCellIdMap();
return vtkm::filter::MapFieldPermutation(field, permutation, result);
}
else if (field.IsWholeDataSetField())
{
result.AddField(field);
return true;
}
return false;
}
VTKM_CONT void ExecuteGenerateNormals(vtkm::cont::DataSet& output,
const vtkm::cont::ArrayHandle<vtkm::Vec3f>& normals)
{
if (this->GenerateNormals)
{
if (this->GetComputeFastNormals())
{
vtkm::filter::vector_analysis::SurfaceNormals surfaceNormals;
surfaceNormals.SetPointNormalsName(this->NormalArrayName);
surfaceNormals.SetGeneratePointNormals(true);
output = surfaceNormals.Execute(output);
}
else
{
output.AddField(vtkm::cont::make_FieldPoint(this->NormalArrayName, normals));
}
}
}
template <typename WorkletType>
VTKM_CONT void ExecuteAddInterpolationEdgeIds(vtkm::cont::DataSet& output, WorkletType& worklet)
{
if (this->AddInterpolationEdgeIds)
{
vtkm::cont::Field interpolationEdgeIdsField(this->InterpolationEdgeIdsArrayName,
vtkm::cont::Field::Association::Points,
worklet.GetInterpolationEdgeIds());
output.AddField(interpolationEdgeIdsField);
}
}
VTKM_CONT
virtual vtkm::cont::DataSet DoExecute(
const vtkm::cont::DataSet& result) = 0; // Needs to be overridden by contour implementations
std::vector<vtkm::Float64> IsoValues;
bool GenerateNormals = true;
bool ComputeFastNormals = false;
bool AddInterpolationEdgeIds = false;
bool MergeDuplicatedPoints = true;
std::string NormalArrayName = "normals";
std::string InterpolationEdgeIdsArrayName = "edgeIds";
};
} // namespace contour
} // namespace filter
} // namespace vtkm
#endif // vtk_m_filter_contour_AbstractContour_h

@ -9,9 +9,12 @@
##============================================================================
set(contour_headers
AbstractContour.h
ClipWithField.h
ClipWithImplicitFunction.h
Contour.h
ContourFlyingEdges.h
ContourMarchingCells.h
MIRFilter.h
Slice.h
)
@ -19,15 +22,23 @@ set(contour_headers
set(contour_sources_device
ClipWithField.cxx
ClipWithImplicitFunction.cxx
Contour.cxx
ContourFlyingEdges.cxx
ContourMarchingCells.cxx
MIRFilter.cxx
Slice.cxx
)
set(contour_sources
# Contour defers worklet compilation to other filters,
# so it does not need to be compiled with a device adapter.
Contour.cxx
)
set_source_files_properties(Contour.cxx PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON)
vtkm_library(
NAME vtkm_filter_contour
SOURCES ${contour_sources}
HEADERS ${contour_headers}
DEVICE_SOURCES ${contour_sources_device}
USE_VTKM_JOB_POOL

@ -7,16 +7,13 @@
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#include <vtkm/cont/ArrayHandleIndex.h>
#include <vtkm/cont/CellSetSingleType.h>
#include <vtkm/cont/CellSetStructured.h>
#include <vtkm/cont/ErrorFilterExecution.h>
#include <vtkm/cont/UnknownCellSet.h>
#include <vtkm/filter/MapFieldPermutation.h>
#include <vtkm/filter/contour/Contour.h>
#include <vtkm/filter/contour/worklet/Contour.h>
#include <vtkm/filter/vector_analysis/SurfaceNormals.h>
#include <vtkm/filter/contour/ContourFlyingEdges.h>
#include <vtkm/filter/contour/ContourMarchingCells.h>
namespace vtkm
{
@ -25,155 +22,48 @@ namespace filter
using SupportedTypes = vtkm::List<vtkm::UInt8, vtkm::Int8, vtkm::Float32, vtkm::Float64>;
namespace
{
inline bool IsCellSetStructured(const vtkm::cont::UnknownCellSet& cellset)
{
if (cellset.template IsType<vtkm::cont::CellSetStructured<1>>() ||
cellset.template IsType<vtkm::cont::CellSetStructured<2>>() ||
cellset.template IsType<vtkm::cont::CellSetStructured<3>>())
{
return true;
}
return false;
}
VTKM_CONT bool DoMapField(vtkm::cont::DataSet& result,
const vtkm::cont::Field& field,
vtkm::worklet::Contour& worklet)
{
if (field.IsPointField())
{
vtkm::cont::UnknownArrayHandle inputArray = field.GetData();
vtkm::cont::UnknownArrayHandle outputArray = inputArray.NewInstanceBasic();
auto functor = [&](const auto& concrete) {
using ComponentType = typename std::decay_t<decltype(concrete)>::ValueType::ComponentType;
auto fieldArray = outputArray.ExtractArrayFromComponents<ComponentType>();
worklet.ProcessPointField(concrete, fieldArray);
};
inputArray.CastAndCallWithExtractedArray(functor);
result.AddPointField(field.GetName(), outputArray);
return true;
}
else if (field.IsCellField())
{
// Use the precompiled field permutation function.
vtkm::cont::ArrayHandle<vtkm::Id> permutation = worklet.GetCellIdMap();
return vtkm::filter::MapFieldPermutation(field, permutation, result);
}
else if (field.IsWholeDataSetField())
{
result.AddField(field);
return true;
}
else
{
return false;
}
}
} // anonymous namespace
namespace contour
{
//-----------------------------------------------------------------------------
void Contour::SetMergeDuplicatePoints(bool on)
{
this->MergeDuplicatedPoints = on;
}
VTKM_CONT
bool Contour::GetMergeDuplicatePoints() const
{
return MergeDuplicatedPoints;
}
//-----------------------------------------------------------------------------
vtkm::cont::DataSet Contour::DoExecute(const vtkm::cont::DataSet& inDataSet)
{
vtkm::worklet::Contour worklet;
worklet.SetMergeDuplicatePoints(this->GetMergeDuplicatePoints());
// Switch between Marching Cubes and Flying Edges implementation of contour,
// depending on the type of CellSet we are processing
if (!this->GetFieldFromDataSet(inDataSet).IsPointField())
vtkm::cont::UnknownCellSet inCellSet = inDataSet.GetCellSet();
auto inCoords = inDataSet.GetCoordinateSystem(this->GetActiveCoordinateSystemIndex()).GetData();
std::unique_ptr<vtkm::filter::contour::AbstractContour> implementation;
// For now, Flying Edges is only used for 3D Structured CellSets,
// using Uniform coordinates.
if (inCellSet.template IsType<vtkm::cont::CellSetStructured<3>>() &&
inCoords.template IsType<
vtkm::cont::ArrayHandle<vtkm::Vec3f, vtkm::cont::StorageTagUniformPoints>>())
{
throw vtkm::cont::ErrorFilterExecution("Point field expected.");
VTKM_LOG_S(vtkm::cont::LogLevel::Info, "Using flying edges");
implementation.reset(new vtkm::filter::contour::ContourFlyingEdges);
implementation->SetComputeFastNormals(this->GetComputeFastNormals());
}
else
{
VTKM_LOG_S(vtkm::cont::LogLevel::Info, "Using marching cells");
implementation.reset(new vtkm::filter::contour::ContourMarchingCells);
implementation->SetComputeFastNormals(this->GetComputeFastNormals());
}
if (this->IsoValues.empty())
implementation->SetMergeDuplicatePoints(this->GetMergeDuplicatePoints());
implementation->SetGenerateNormals(this->GetGenerateNormals());
implementation->SetAddInterpolationEdgeIds(this->GetAddInterpolationEdgeIds());
implementation->SetNormalArrayName(this->GetNormalArrayName());
implementation->SetActiveField(this->GetActiveFieldName());
implementation->SetFieldsToPass(this->GetFieldsToPass());
implementation->SetNumberOfIsoValues(this->GetNumberOfIsoValues());
for (int i = 0; i < this->GetNumberOfIsoValues(); i++)
{
throw vtkm::cont::ErrorFilterExecution("No iso-values provided.");
implementation->SetIsoValue(i, this->GetIsoValue(i));
}
//get the inputCells and coordinates of the dataset
const vtkm::cont::UnknownCellSet& inputCells = inDataSet.GetCellSet();
const vtkm::cont::CoordinateSystem& inputCoords =
inDataSet.GetCoordinateSystem(this->GetActiveCoordinateSystemIndex());
using Vec3HandleType = vtkm::cont::ArrayHandle<vtkm::Vec3f>;
Vec3HandleType vertices;
Vec3HandleType normals;
vtkm::cont::CellSetSingleType<> outputCells;
bool generateHighQualityNormals = IsCellSetStructured(inputCells)
? !this->ComputeFastNormalsForStructured
: !this->ComputeFastNormalsForUnstructured;
auto resolveFieldType = [&](const auto& concrete) {
// use std::decay to remove const ref from the decltype of concrete.
using T = typename std::decay_t<decltype(concrete)>::ValueType;
std::vector<T> ivalues(this->IsoValues.size());
for (std::size_t i = 0; i < ivalues.size(); ++i)
{
ivalues[i] = static_cast<T>(this->IsoValues[i]);
}
if (this->GenerateNormals && generateHighQualityNormals)
{
outputCells =
worklet.Run(ivalues, inputCells, inputCoords.GetData(), concrete, vertices, normals);
}
else
{
outputCells = worklet.Run(ivalues, inputCells, inputCoords.GetData(), concrete, vertices);
}
};
this->GetFieldFromDataSet(inDataSet)
.GetData()
.CastAndCallForTypesWithFloatFallback<SupportedTypes, VTKM_DEFAULT_STORAGE_LIST>(
resolveFieldType);
auto mapper = [&](auto& result, const auto& f) { DoMapField(result, f, worklet); };
vtkm::cont::DataSet output = this->CreateResultCoordinateSystem(
inDataSet, outputCells, inputCoords.GetName(), vertices, mapper);
if (this->GenerateNormals)
{
if (!generateHighQualityNormals)
{
vtkm::filter::vector_analysis::SurfaceNormals surfaceNormals;
surfaceNormals.SetPointNormalsName(this->NormalArrayName);
surfaceNormals.SetGeneratePointNormals(true);
output = surfaceNormals.Execute(output);
}
else
{
output.AddField(vtkm::cont::make_FieldPoint(this->NormalArrayName, normals));
}
}
if (this->AddInterpolationEdgeIds)
{
vtkm::cont::Field interpolationEdgeIdsField(InterpolationEdgeIdsArrayName,
vtkm::cont::Field::Association::Points,
worklet.GetInterpolationEdgeIds());
output.AddField(interpolationEdgeIdsField);
}
return output;
return implementation->Execute(inDataSet);
}
} // namespace contour
} // namespace filter

@ -11,7 +11,7 @@
#ifndef vtk_m_filter_contour_Contour_h
#define vtk_m_filter_contour_Contour_h
#include <vtkm/filter/FilterField.h>
#include <vtkm/filter/contour/AbstractContour.h>
#include <vtkm/filter/contour/vtkm_filter_contour_export.h>
namespace vtkm
@ -25,103 +25,33 @@ namespace contour
/// Takes as input a volume (e.g., 3D structured point set) and generates on
/// output one or more isosurfaces.
/// Multiple contour values must be specified to generate the isosurfaces.
/// This filter automatically selects the right implmentation between Marching Cells
/// and Flying Edges algorithms depending on the type of input \c DataSet : Flying Edges
/// is only available for 3-dimensional datasets using uniform point coordinates.
/// @warning
/// This filter is currently only supports 3D volumes.
class VTKM_FILTER_CONTOUR_EXPORT Contour : public vtkm::filter::FilterField
class VTKM_FILTER_CONTOUR_EXPORT Contour : public vtkm::filter::contour::AbstractContour
{
public:
void SetNumberOfIsoValues(vtkm::Id num)
{
if (num >= 0)
{
this->IsoValues.resize(static_cast<std::size_t>(num));
}
}
vtkm::Id GetNumberOfIsoValues() const { return static_cast<vtkm::Id>(this->IsoValues.size()); }
void SetIsoValue(vtkm::Float64 v) { this->SetIsoValue(0, v); }
void SetIsoValue(vtkm::Id index, vtkm::Float64 v)
{
std::size_t i = static_cast<std::size_t>(index);
if (i >= this->IsoValues.size())
{
this->IsoValues.resize(i + 1);
}
this->IsoValues[i] = v;
}
void SetIsoValues(const std::vector<vtkm::Float64>& values) { this->IsoValues = values; }
vtkm::Float64 GetIsoValue(vtkm::Id index) const
{
return this->IsoValues[static_cast<std::size_t>(index)];
}
/// Set/Get whether the points generated should be unique for every triangle
/// or will duplicate points be merged together. Duplicate points are identified
/// by the unique edge it was generated from.
///
VTKM_CONT
void SetMergeDuplicatePoints(bool on);
VTKM_CONT
bool GetMergeDuplicatePoints() const;
/// Set/Get whether normals should be generated. Off by default. If enabled,
/// the default behaviour is to generate high quality normals for structured
/// datasets, using gradients, and generate fast normals for unstructured
/// datasets based on the result triangle mesh.
///
VTKM_CONT
void SetGenerateNormals(bool on) { this->GenerateNormals = on; }
VTKM_CONT
bool GetGenerateNormals() const { return this->GenerateNormals; }
/// Set/Get whether to append the ids of the intersected edges to the vertices of the isosurface triangles. Off by default.
VTKM_CONT
void SetAddInterpolationEdgeIds(bool on) { this->AddInterpolationEdgeIds = on; }
VTKM_CONT
bool GetAddInterpolationEdgeIds() const { return this->AddInterpolationEdgeIds; }
/// Set/Get whether the fast path should be used for normals computation for
/// structured datasets. Off by default.
VTKM_CONT
void SetComputeFastNormalsForStructured(bool on) { this->ComputeFastNormalsForStructured = on; }
VTKM_CONT
bool GetComputeFastNormalsForStructured() const { return this->ComputeFastNormalsForStructured; }
VTKM_DEPRECATED(2.1, "Use SetComputeFastNormals.")
VTKM_CONT void SetComputeFastNormalsForStructured(bool on) { this->SetComputeFastNormals(on); }
VTKM_DEPRECATED(2.1, "Use GetComputeFastNormals.")
VTKM_CONT bool GetComputeFastNormalsForStructured() const
{
return this->GetComputeFastNormals();
}
/// Set/Get whether the fast path should be used for normals computation for
/// unstructured datasets. On by default.
VTKM_CONT
void SetComputeFastNormalsForUnstructured(bool on)
VTKM_DEPRECATED(2.1, "Use SetComputeFastNormals.")
VTKM_CONT void SetComputeFastNormalsForUnstructured(bool on) { this->SetComputeFastNormals(on); }
VTKM_DEPRECATED(2.1, "Use GetComputeFastNormals.")
VTKM_CONT bool GetComputeFastNormalsForUnstructured() const
{
this->ComputeFastNormalsForUnstructured = on;
return this->GetComputeFastNormals();
}
VTKM_CONT
bool GetComputeFastNormalsForUnstructured() const
{
return this->ComputeFastNormalsForUnstructured;
}
VTKM_CONT
void SetNormalArrayName(const std::string& name) { this->NormalArrayName = name; }
VTKM_CONT
const std::string& GetNormalArrayName() const { return this->NormalArrayName; }
private:
VTKM_CONT
std::vector<vtkm::Float64> IsoValues;
bool GenerateNormals = false;
bool AddInterpolationEdgeIds = false;
bool ComputeFastNormalsForStructured = false;
bool ComputeFastNormalsForUnstructured = true;
bool MergeDuplicatedPoints = true;
std::string NormalArrayName = "normals";
std::string InterpolationEdgeIdsArrayName = "edgeIds";
protected:
// Needed by the subclass Slice

@ -0,0 +1,112 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#include <vtkm/cont/CellSetSingleType.h>
#include <vtkm/cont/CellSetStructured.h>
#include <vtkm/cont/ErrorFilterExecution.h>
#include <vtkm/cont/UnknownCellSet.h>
#include <vtkm/filter/contour/ContourFlyingEdges.h>
#include <vtkm/filter/contour/worklet/ContourFlyingEdges.h>
namespace vtkm
{
namespace filter
{
using SupportedTypes = vtkm::List<vtkm::UInt8, vtkm::Int8, vtkm::Float32, vtkm::Float64>;
namespace contour
{
//-----------------------------------------------------------------------------
vtkm::cont::DataSet ContourFlyingEdges::DoExecute(const vtkm::cont::DataSet& inDataSet)
{
vtkm::worklet::ContourFlyingEdges worklet;
worklet.SetMergeDuplicatePoints(this->GetMergeDuplicatePoints());
if (!this->GetFieldFromDataSet(inDataSet).IsPointField())
{
throw vtkm::cont::ErrorFilterExecution("Point field expected.");
}
if (this->IsoValues.empty())
{
throw vtkm::cont::ErrorFilterExecution("No iso-values provided.");
}
vtkm::cont::UnknownCellSet inCellSet = inDataSet.GetCellSet();
const vtkm::cont::CoordinateSystem& inCoords =
inDataSet.GetCoordinateSystem(this->GetActiveCoordinateSystemIndex());
if (!inCellSet.template IsType<vtkm::cont::CellSetStructured<3>>() ||
!inCoords.GetData()
.template IsType<
vtkm::cont::ArrayHandle<vtkm::Vec3f, vtkm::cont::StorageTagUniformPoints>>())
{
throw vtkm::cont::ErrorFilterExecution("This filter is only available for 3-Dimensional "
"Structured Cell Sets using uniform point coordinates.");
}
// Get the CellSet's known dynamic type
const vtkm::cont::CellSetStructured<3>& inputCells =
inDataSet.GetCellSet().AsCellSet<vtkm::cont::CellSetStructured<3>>();
using Vec3HandleType = vtkm::cont::ArrayHandle<vtkm::Vec3f>;
Vec3HandleType vertices;
Vec3HandleType normals;
vtkm::cont::CellSetSingleType<> outputCells;
auto resolveFieldType = [&](const auto& concrete) {
// use std::decay to remove const ref from the decltype of concrete.
using T = typename std::decay_t<decltype(concrete)>::ValueType;
std::vector<T> ivalues(this->IsoValues.size());
for (std::size_t i = 0; i < ivalues.size(); ++i)
{
ivalues[i] = static_cast<T>(this->IsoValues[i]);
}
if (this->GenerateNormals && !this->GetComputeFastNormals())
{
outputCells = worklet.Run(
ivalues,
inputCells,
inCoords.GetData().AsArrayHandle<vtkm::cont::ArrayHandleUniformPointCoordinates>(),
concrete,
vertices,
normals);
}
else
{
outputCells = worklet.Run(
ivalues,
inputCells,
inCoords.GetData().AsArrayHandle<vtkm::cont::ArrayHandleUniformPointCoordinates>(),
concrete,
vertices);
}
};
this->GetFieldFromDataSet(inDataSet)
.GetData()
.CastAndCallForTypesWithFloatFallback<SupportedTypes, VTKM_DEFAULT_STORAGE_LIST>(
resolveFieldType);
auto mapper = [&](auto& result, const auto& f) { this->DoMapField(result, f, worklet); };
vtkm::cont::DataSet output = this->CreateResultCoordinateSystem(
inDataSet, outputCells, inCoords.GetName(), vertices, mapper);
this->ExecuteGenerateNormals(output, normals);
this->ExecuteAddInterpolationEdgeIds(output, worklet);
return output;
}
} // namespace contour
} // namespace filter
} // namespace vtkm

@ -0,0 +1,41 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#ifndef vtk_m_filter_contour_ContourFlyingEdges_h
#define vtk_m_filter_contour_ContourFlyingEdges_h
#include <vtkm/filter/contour/AbstractContour.h>
#include <vtkm/filter/contour/vtkm_filter_contour_export.h>
namespace vtkm
{
namespace filter
{
namespace contour
{
/// \brief generate isosurface(s) from a 3-dimensional structured mesh
/// Takes as input a 3D structured mesh and generates on
/// output one or more isosurfaces using the Flying Edges algorithm.
/// Multiple contour values must be specified to generate the isosurfaces.
///
/// This implementation only accepts \c CellSetStructured<3> inputs using
/// \c ArrayHandleUniformPointCoordinates for point coordinates,
/// and is only used as part of the more general \c Contour filter
class VTKM_FILTER_CONTOUR_EXPORT ContourFlyingEdges : public vtkm::filter::contour::AbstractContour
{
protected:
VTKM_CONT vtkm::cont::DataSet DoExecute(const vtkm::cont::DataSet& result) override;
};
} // namespace contour
} // namespace filter
} // namespace vtkm
#endif // vtk_m_filter_contour_ContourFlyingEdges_h

@ -0,0 +1,85 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#include <vtkm/cont/CellSetSingleType.h>
#include <vtkm/cont/ErrorFilterExecution.h>
#include <vtkm/cont/UnknownCellSet.h>
#include <vtkm/filter/contour/ContourMarchingCells.h>
#include <vtkm/filter/contour/worklet/ContourMarchingCells.h>
namespace vtkm
{
namespace filter
{
namespace contour
{
//-----------------------------------------------------------------------------
vtkm::cont::DataSet ContourMarchingCells::DoExecute(const vtkm::cont::DataSet& inDataSet)
{
vtkm::worklet::ContourMarchingCells worklet;
worklet.SetMergeDuplicatePoints(this->GetMergeDuplicatePoints());
if (!this->GetFieldFromDataSet(inDataSet).IsPointField())
{
throw vtkm::cont::ErrorFilterExecution("Point field expected.");
}
if (this->IsoValues.empty())
{
throw vtkm::cont::ErrorFilterExecution("No iso-values provided.");
}
//get the inputCells and coordinates of the dataset
const vtkm::cont::UnknownCellSet& inputCells = inDataSet.GetCellSet();
const vtkm::cont::CoordinateSystem& inputCoords =
inDataSet.GetCoordinateSystem(this->GetActiveCoordinateSystemIndex());
using Vec3HandleType = vtkm::cont::ArrayHandle<vtkm::Vec3f>;
Vec3HandleType vertices;
Vec3HandleType normals;
vtkm::cont::CellSetSingleType<> outputCells;
auto resolveFieldType = [&](const auto& concrete) {
// use std::decay to remove const ref from the decltype of concrete.
using T = typename std::decay_t<decltype(concrete)>::ValueType;
std::vector<T> ivalues(this->IsoValues.size());
for (std::size_t i = 0; i < ivalues.size(); ++i)
{
ivalues[i] = static_cast<T>(this->IsoValues[i]);
}
if (this->GenerateNormals && !this->GetComputeFastNormals())
{
outputCells =
worklet.Run(ivalues, inputCells, inputCoords.GetData(), concrete, vertices, normals);
}
else
{
outputCells = worklet.Run(ivalues, inputCells, inputCoords.GetData(), concrete, vertices);
}
};
this->CastAndCallScalarField(this->GetFieldFromDataSet(inDataSet), resolveFieldType);
auto mapper = [&](auto& result, const auto& f) { this->DoMapField(result, f, worklet); };
vtkm::cont::DataSet output = this->CreateResultCoordinateSystem(
inDataSet, outputCells, inputCoords.GetName(), vertices, mapper);
this->ExecuteGenerateNormals(output, normals);
this->ExecuteAddInterpolationEdgeIds(output, worklet);
return output;
}
} // namespace contour
} // namespace filter
} // namespace vtkm

@ -0,0 +1,43 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#ifndef vtk_m_filter_contour_ContourMarchingCells_h
#define vtk_m_filter_contour_ContourMarchingCells_h
#include <vtkm/filter/contour/AbstractContour.h>
#include <vtkm/filter/contour/vtkm_filter_contour_export.h>
namespace vtkm
{
namespace filter
{
namespace contour
{
/// \brief generate isosurface(s) from a Volume using the Marching Cells algorithm
///
/// Takes as input a volume (e.g., 3D structured point set) and generates on
/// output one or more isosurfaces.
/// Multiple contour values must be specified to generate the isosurfaces.
///
/// This implementation is not optimized for all use cases, it is used by
/// the more general \c Contour filter which selects the best implementation
/// for all types of \c DataSet . .
class VTKM_FILTER_CONTOUR_EXPORT ContourMarchingCells
: public vtkm::filter::contour::AbstractContour
{
protected:
VTKM_CONT
vtkm::cont::DataSet DoExecute(const vtkm::cont::DataSet& result) override;
};
} // namespace contour
} // namespace filter
} // namespace vtkm
#endif // vtk_m_filter_contour_ContourMarchingCells_h

@ -11,10 +11,13 @@
#include <vtkm/Math.h>
#include <vtkm/cont/Algorithm.h>
#include <vtkm/cont/DataSet.h>
#include <vtkm/cont/ErrorFilterExecution.h>
#include <vtkm/cont/testing/MakeTestDataSet.h>
#include <vtkm/cont/testing/Testing.h>
#include <vtkm/filter/contour/Contour.h>
#include <vtkm/filter/contour/ContourFlyingEdges.h>
#include <vtkm/filter/contour/ContourMarchingCells.h>
#include <vtkm/filter/field_transform/GenerateIds.h>
#include <vtkm/io/VTKDataSetReader.h>
@ -26,7 +29,8 @@ namespace
class TestContourFilter
{
public:
void TestContourUniformGrid() const
template <typename ContourFilterType>
void TestContourUniformGrid(vtkm::IdComponent numPointsNoMergeDuplicate) const
{
std::cout << "Testing Contour filter on a uniform grid" << std::endl;
@ -38,14 +42,14 @@ public:
genIds.SetCellFieldName("cellvar");
vtkm::cont::DataSet dataSet = genIds.Execute(tangle.Execute());
vtkm::filter::contour::Contour mc;
ContourFilterType filter;
mc.SetGenerateNormals(true);
mc.SetIsoValue(0, 0.5);
mc.SetActiveField("tangle");
mc.SetFieldsToPass(vtkm::filter::FieldSelection::Mode::None);
filter.SetGenerateNormals(true);
filter.SetIsoValue(0, 0.5);
filter.SetActiveField("tangle");
filter.SetFieldsToPass(vtkm::filter::FieldSelection::Mode::None);
auto result = mc.Execute(dataSet);
auto result = filter.Execute(dataSet);
{
VTKM_TEST_ASSERT(result.GetNumberOfCoordinateSystems() == 1,
"Wrong number of coordinate systems in the output dataset");
@ -55,8 +59,8 @@ public:
}
// let's execute with mapping fields.
mc.SetFieldsToPass({ "tangle", "cellvar" });
result = mc.Execute(dataSet);
filter.SetFieldsToPass({ "tangle", "cellvar" });
result = filter.Execute(dataSet);
{
const bool isMapped = result.HasField("tangle");
VTKM_TEST_ASSERT(isMapped, "mapping should pass");
@ -99,16 +103,13 @@ public:
VTKM_TEST_ASSERT(cells.GetNumberOfCells() == 160, "");
}
//Now try with vertex merging disabled. Since this
//we use FlyingEdges we now which does point merging for free
//so we should see the number of points not change
mc.SetMergeDuplicatePoints(false);
mc.SetFieldsToPass(vtkm::filter::FieldSelection::Mode::All);
result = mc.Execute(dataSet);
//Now try with vertex merging disabled.
filter.SetMergeDuplicatePoints(false);
filter.SetFieldsToPass(vtkm::filter::FieldSelection::Mode::All);
result = filter.Execute(dataSet);
{
vtkm::cont::CoordinateSystem coords = result.GetCoordinateSystem();
VTKM_TEST_ASSERT(coords.GetNumberOfPoints() == 72,
VTKM_TEST_ASSERT(coords.GetNumberOfPoints() == numPointsNoMergeDuplicate,
"Shouldn't have less coordinates than the unmerged version");
//verify that the number of cells is correct (160)
@ -120,20 +121,24 @@ public:
}
}
template <typename ContourFilterType>
void Test3DUniformDataSet0() const
{
vtkm::cont::testing::MakeTestDataSet maker;
vtkm::cont::DataSet inputData = maker.Make3DUniformDataSet0();
std::string fieldName = "pointvar";
// Defend the test against changes to Make3DUniformDataSet0():
VTKM_TEST_ASSERT(inputData.HasField(fieldName));
vtkm::cont::Field pointField = inputData.GetField(fieldName);
vtkm::Range range;
pointField.GetRange(&range);
vtkm::FloatDefault isovalue = 100.0;
// Range = [10.1, 180.5]
VTKM_TEST_ASSERT(range.Contains(isovalue));
vtkm::filter::contour::Contour filter;
ContourFilterType filter;
filter.SetGenerateNormals(false);
filter.SetMergeDuplicatePoints(true);
filter.SetIsoValue(isovalue);
@ -143,6 +148,7 @@ public:
VTKM_TEST_ASSERT(outputData.GetNumberOfPoints() == 9);
}
template <typename ContourFilterType>
void TestContourWedges() const
{
std::cout << "Testing Contour filter on wedge cells" << std::endl;
@ -158,7 +164,7 @@ public:
vtkm::cont::ArrayHandle<vtkm::Float32> fieldArray;
dataSet.GetPointField("gyroid").GetData().AsArrayHandle(fieldArray);
vtkm::filter::contour::Contour isosurfaceFilter;
ContourFilterType isosurfaceFilter;
isosurfaceFilter.SetActiveField("gyroid");
isosurfaceFilter.SetMergeDuplicatePoints(false);
isosurfaceFilter.SetIsoValue(0.0);
@ -167,11 +173,54 @@ public:
VTKM_TEST_ASSERT(result.GetNumberOfCells() == 52);
}
void TestUnsupportedFlyingEdges() const
{
vtkm::cont::testing::MakeTestDataSet maker;
vtkm::cont::DataSet rectilinearDataset = maker.Make3DRectilinearDataSet0();
vtkm::cont::DataSet explicitDataSet = maker.Make3DExplicitDataSet0();
vtkm::filter::contour::ContourFlyingEdges filter;
filter.SetIsoValue(2.0);
filter.SetActiveField("pointvar");
try
{
filter.Execute(rectilinearDataset);
VTKM_TEST_FAIL("Flying Edges filter should not run on datasets with rectilinear coordinates");
}
catch (vtkm::cont::ErrorFilterExecution&)
{
std::cout << "Execution successfully aborted" << std::endl;
}
try
{
filter.Execute(explicitDataSet);
VTKM_TEST_FAIL("Flying Edges filter should not run on explicit datasets");
}
catch (vtkm::cont::ErrorFilterExecution&)
{
std::cout << "Execution successfully aborted" << std::endl;
}
}
void operator()() const
{
this->Test3DUniformDataSet0();
this->TestContourUniformGrid();
this->TestContourWedges();
this->TestContourUniformGrid<vtkm::filter::contour::Contour>(72);
this->TestContourUniformGrid<vtkm::filter::contour::ContourFlyingEdges>(72);
// Unlike flying edges, marching cells does not have point merging for free,
// So the number of points should increase when disabling duplicate point merging.
this->TestContourUniformGrid<vtkm::filter::contour::ContourMarchingCells>(480);
this->Test3DUniformDataSet0<vtkm::filter::contour::Contour>();
this->Test3DUniformDataSet0<vtkm::filter::contour::ContourMarchingCells>();
this->Test3DUniformDataSet0<vtkm::filter::contour::ContourFlyingEdges>();
this->TestContourWedges<vtkm::filter::contour::Contour>();
this->TestContourWedges<vtkm::filter::contour::ContourMarchingCells>();
this->TestUnsupportedFlyingEdges();
}
}; // class TestContourFilter

@ -105,6 +105,14 @@ void TestNormals(const vtkm::cont::DataSet& dataset, bool structured)
vtkm::filter::contour::Contour mc;
mc.SetIsoValue(0, 200);
mc.SetGenerateNormals(true);
if (structured)
{
mc.SetComputeFastNormals(false);
}
else
{
mc.SetComputeFastNormals(true);
}
// Test default normals generation: high quality for structured, fast for unstructured.
auto expected = structured ? hq_sg : fast;
@ -136,7 +144,7 @@ void TestNormals(const vtkm::cont::DataSet& dataset, bool structured)
// Test the other normals generation method
if (structured)
{
mc.SetComputeFastNormalsForStructured(true);
mc.SetComputeFastNormals(true);
expected = fast;
if (using_fe_y_alg_ordering)
{
@ -145,7 +153,7 @@ void TestNormals(const vtkm::cont::DataSet& dataset, bool structured)
}
else
{
mc.SetComputeFastNormalsForUnstructured(false);
mc.SetComputeFastNormals(false);
expected = hq_ug;
}

@ -10,7 +10,8 @@
set(headers
Clip.h
Contour.h
ContourFlyingEdges.h
ContourMarchingCells.h
MIR.h
)

@ -0,0 +1,114 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#ifndef vtk_m_worklet_ContourFlyingEdges_h
#define vtk_m_worklet_ContourFlyingEdges_h
#include <vtkm/cont/ArrayHandleUniformPointCoordinates.h>
#include <vtkm/filter/contour/worklet/contour/CommonState.h>
#include <vtkm/filter/contour/worklet/contour/FieldPropagation.h>
#include <vtkm/filter/contour/worklet/contour/FlyingEdges.h>
namespace vtkm
{
namespace worklet
{
/// \brief Compute the isosurface of a given \c CellSetStructured<3> input with
/// \c ArrayHandleUniformPointCoordinates for point coordinates using the Flying Edges algorithm.
class ContourFlyingEdges
{
public:
//----------------------------------------------------------------------------
ContourFlyingEdges(bool mergeDuplicates = true)
: SharedState(mergeDuplicates)
{
}
//----------------------------------------------------------------------------
vtkm::cont::ArrayHandle<vtkm::Id2> GetInterpolationEdgeIds() const
{
return this->SharedState.InterpolationEdgeIds;
}
//----------------------------------------------------------------------------
void SetMergeDuplicatePoints(bool merge) { this->SharedState.MergeDuplicatePoints = merge; }
//----------------------------------------------------------------------------
bool GetMergeDuplicatePoints() const { return this->SharedState.MergeDuplicatePoints; }
//----------------------------------------------------------------------------
vtkm::cont::ArrayHandle<vtkm::Id> GetCellIdMap() const { return this->SharedState.CellIdMap; }
//----------------------------------------------------------------------------
template <typename InArrayType, typename OutArrayType>
void ProcessPointField(const InArrayType& input, const OutArrayType& output) const
{
using vtkm::worklet::contour::MapPointField;
vtkm::worklet::DispatcherMapField<MapPointField> applyFieldDispatcher;
applyFieldDispatcher.Invoke(this->SharedState.InterpolationEdgeIds,
this->SharedState.InterpolationWeights,
input,
output);
}
//----------------------------------------------------------------------------
void ReleaseCellMapArrays() { this->SharedState.CellIdMap.ReleaseResources(); }
// Filter called without normals generation
template <typename ValueType,
typename StorageTagField,
typename CoordinateType,
typename StorageTagVertices>
vtkm::cont::CellSetSingleType<> Run(
const std::vector<ValueType>& isovalues,
const vtkm::cont::CellSetStructured<3>& cells,
const vtkm::cont::ArrayHandleUniformPointCoordinates& coordinateSystem,
const vtkm::cont::ArrayHandle<ValueType, StorageTagField>& input,
vtkm::cont::ArrayHandle<vtkm::Vec<CoordinateType, 3>, StorageTagVertices>& vertices)
{
this->SharedState.GenerateNormals = false;
vtkm::cont::ArrayHandle<vtkm::Vec<CoordinateType, 3>> normals;
vtkm::cont::CellSetSingleType<> outputCells;
return flying_edges::execute(
cells, coordinateSystem, isovalues, input, vertices, normals, this->SharedState);
}
// Filter called with normals generation
template <typename ValueType,
typename StorageTagField,
typename CoordinateType,
typename StorageTagVertices,
typename StorageTagNormals>
vtkm::cont::CellSetSingleType<> Run(
const std::vector<ValueType>& isovalues,
const vtkm::cont::CellSetStructured<3>& cells,
const vtkm::cont::ArrayHandleUniformPointCoordinates& coordinateSystem,
const vtkm::cont::ArrayHandle<ValueType, StorageTagField>& input,
vtkm::cont::ArrayHandle<vtkm::Vec<CoordinateType, 3>, StorageTagVertices>& vertices,
vtkm::cont::ArrayHandle<vtkm::Vec<CoordinateType, 3>, StorageTagNormals>& normals)
{
this->SharedState.GenerateNormals = true;
vtkm::cont::CellSetSingleType<> outputCells;
return flying_edges::execute(
cells, coordinateSystem, isovalues, input, vertices, normals, this->SharedState);
}
private:
vtkm::worklet::contour::CommonState SharedState;
};
}
} // namespace vtkm::worklet
#endif // vtk_m_worklet_ContourFlyingEdges_h

@ -8,16 +8,11 @@
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#ifndef vtk_m_worklet_Contour_h
#define vtk_m_worklet_Contour_h
#include <vtkm/cont/ArrayCopy.h>
#include <vtkm/cont/ArrayHandlePermutation.h>
#include <vtkm/cont/ArrayHandleUniformPointCoordinates.h>
#ifndef vtk_m_worklet_ContourMarchingCells_h
#define vtk_m_worklet_ContourMarchingCells_h
#include <vtkm/filter/contour/worklet/contour/CommonState.h>
#include <vtkm/filter/contour/worklet/contour/FieldPropagation.h>
#include <vtkm/filter/contour/worklet/contour/FlyingEdges.h>
#include <vtkm/filter/contour/worklet/contour/MarchingCells.h>
@ -25,8 +20,6 @@ namespace vtkm
{
namespace worklet
{
namespace contour
{
struct DeduceCoordType
@ -39,16 +32,6 @@ struct DeduceCoordType
{
result = marching_cells::execute(cells, coords, std::forward<Args>(args)...);
}
template <typename... Args>
void operator()(
const vtkm::cont::ArrayHandle<vtkm::Vec3f, vtkm::cont::StorageTagUniformPoints>& coords,
const vtkm::cont::CellSetStructured<3>& cells,
vtkm::cont::CellSetSingleType<>& result,
Args&&... args) const
{
result = flying_edges::execute(cells, coords, std::forward<Args>(args)...);
}
};
struct DeduceCellType
@ -62,13 +45,12 @@ struct DeduceCellType
};
}
/// \brief Compute the isosurface of a given 3D data set, supports all
/// linear cell types
class Contour
/// \brief Compute the isosurface of a given 3D data set, supports all linear cell types
class ContourMarchingCells
{
public:
//----------------------------------------------------------------------------
Contour(bool mergeDuplicates = true)
ContourMarchingCells(bool mergeDuplicates = true)
: SharedState(mergeDuplicates)
{
}
@ -89,6 +71,23 @@ public:
vtkm::cont::ArrayHandle<vtkm::Id> GetCellIdMap() const { return this->SharedState.CellIdMap; }
//----------------------------------------------------------------------------
template <typename InArrayType, typename OutArrayType>
void ProcessPointField(const InArrayType& input, const OutArrayType& output) const
{
using vtkm::worklet::contour::MapPointField;
vtkm::worklet::DispatcherMapField<MapPointField> applyFieldDispatcher;
applyFieldDispatcher.Invoke(this->SharedState.InterpolationEdgeIds,
this->SharedState.InterpolationWeights,
input,
output);
}
//----------------------------------------------------------------------------
void ReleaseCellMapArrays() { this->SharedState.CellIdMap.ReleaseResources(); }
// Filter called without normals generation
template <typename ValueType,
typename CellSetType,
typename CoordinateSystem,
@ -118,7 +117,7 @@ public:
return outputCells;
}
//----------------------------------------------------------------------------
// Filter called with normals generation
template <typename ValueType,
typename CellSetType,
typename CoordinateSystem,
@ -149,22 +148,6 @@ public:
return outputCells;
}
//----------------------------------------------------------------------------
template <typename InArrayType, typename OutArrayType>
void ProcessPointField(const InArrayType& input, const OutArrayType& output) const
{
using vtkm::worklet::contour::MapPointField;
vtkm::worklet::DispatcherMapField<MapPointField> applyFieldDispatcher;
applyFieldDispatcher.Invoke(this->SharedState.InterpolationEdgeIds,
this->SharedState.InterpolationWeights,
input,
output);
}
//----------------------------------------------------------------------------
void ReleaseCellMapArrays() { this->SharedState.CellIdMap.ReleaseResources(); }
private:
vtkm::worklet::contour::CommonState SharedState;
@ -172,4 +155,4 @@ private:
}
} // namespace vtkm::worklet
#endif // vtk_m_worklet_Contour_h
#endif // vtk_m_worklet_ContourMarchingCells_h

@ -234,7 +234,7 @@ void TestWithStructuredData()
contour.SetIsoValue(192);
contour.SetMergeDuplicatePoints(true);
contour.SetGenerateNormals(true);
contour.SetComputeFastNormalsForStructured(true);
contour.SetComputeFastNormals(true);
contour.SetNormalArrayName("normals");
dataSet = contour.Execute(dataSet);