Commit 246a5830 authored by Haocheng LIU's avatar Haocheng LIU
Browse files

Add a split sharp edge worklet and filter

It's a filter that Split sharp manifold edges where the feature angle
between the adjacent surfaces are larger than the threshold value.
When an edge is split, it would add a new point to the coordinates
and update the connectivity of an adjacent surface.
Ex. there are two adjacent triangles(0,1,2) and (2,1,3). Edge (1,2) needs
to be split. Two new points 4(duplication of point 1) an 5(duplication of point 2)
would be added and the later triangle's connectivity would be changed
to (5,4,3).
By default, all old point's fields would be copied to the new point.
Use with caution.
parent 65439425
Add a split sharp edge filter
It's a filter that splits sharp manifold edges where the feature angle
between the adjacent surfaces are larger than the threshold value.
When an edge is split, it would add a new point to the coordinates
and update the connectivity of an adjacent surface.
Ex. There are two adjacent triangles(0,1,2) and (2,1,3). Edge (1,2) needs
to be split. Two new points 4(duplication of point 1) an 5(duplication of point 2)
would be added and the later triangle's connectivity would be changed
to (5,4,3).
By default, all old point's fields would be copied to the new point.
Use with caution.
//============================================================================
// 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.
//
// Copyright 2016 National Technology & Engineering Solutions of Sandia, LLC (NTESS).
// Copyright 2016 UT-Battelle, LLC.
// Copyright 2016 Los Alamos National Security.
//
// Under the terms of Contract DE-NA0003525 with NTESS,
// the U.S. Government retains certain rights in this software.
//
// Under the terms of Contract DE-AC52-06NA25396 with Los Alamos National
// Laboratory (LANL), the U.S. Government retains certain rights in
// this software.
//============================================================================
#ifndef vtk_m_Bitset_h
#define vtk_m_Bitset_h
#include <assert.h>
#include <limits>
#include <vtkm/Types.h>
#include <vtkm/internal/ExportMacros.h>
namespace vtkm
{
/// \brief A bitmap to serve different needs.
/// Ex. Editing particular bits in a byte(s), checkint if particular bit values
/// are present or not. Once Cuda supports std::bitset, we should use the
/// standard one if possible
template <typename MaskType>
struct Bitset
{
VTKM_EXEC_CONT Bitset()
: Mask(0)
{
}
VTKM_EXEC_CONT void set(vtkm::Id bitIndex)
{
this->Mask = this->Mask | (static_cast<MaskType>(1) << bitIndex);
}
VTKM_EXEC_CONT void reset(vtkm::Id bitIndex)
{
this->Mask = this->Mask & ~(static_cast<MaskType>(1) << bitIndex);
}
VTKM_EXEC_CONT void toggle(vtkm::Id bitIndex)
{
this->Mask = this->Mask ^ (static_cast<MaskType>(0) << bitIndex);
}
VTKM_EXEC_CONT bool test(vtkm::Id bitIndex)
{
return ((this->Mask & (static_cast<MaskType>(1) << bitIndex)) != 0);
}
private:
MaskType Mask;
};
} // namespace vtkm
#endif //vtk_m_Bitset_h
......@@ -31,6 +31,7 @@ set(headers
BaseComponent.h
BinaryPredicates.h
BinaryOperators.h
Bitset.h
Bounds.h
CellShape.h
CellTraits.h
......
......@@ -59,6 +59,7 @@ set(headers
PolicyBase.h
PolicyDefault.h
Probe.h
SplitSharpEdges.h
Streamline.h
SurfaceNormals.h
Tetrahedralize.h
......@@ -107,6 +108,7 @@ set(header_template_sources
PointElevation.hxx
PointTransform.hxx
Probe.hxx
SplitSharpEdges.hxx
Streamline.hxx
SurfaceNormals.hxx
Tetrahedralize.hxx
......
......@@ -84,7 +84,7 @@ template <>
class FilterTraits<SphericalCoordinateTransform>
{
public:
//Point Elevation can only convert Float and Double Vec3 arrays
//CoordinateSystemTransformation can only convert Float and Double Vec3 arrays
using InputFieldTypeList = vtkm::TypeListTagFieldVec3;
};
}
......
......@@ -75,7 +75,7 @@ template <typename S>
class FilterTraits<PointTransform<S>>
{
public:
//Point Elevation can only convert Float and Double Vec3 arrays
//PointTransformation can only convert Float and Double Vec3 arrays
using InputFieldTypeList = vtkm::TypeListTagFieldVec3;
};
}
......
//============================================================================
// 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.
//
// Copyright 2014 National Technology & Engineering Solutions of Sandia, LLC (NTESS).
// Copyright 2014 UT-Battelle, LLC.
// Copyright 2014 Los Alamos National Security.
//
// Under the terms of Contract DE-NA0003525 with NTESS,
// the U.S. Government retains certain rights in this software.
//
// Under the terms of Contract DE-AC52-06NA25396 with Los Alamos National
// Laboratory (LANL), the U.S. Government retains certain rights in
// this software.
//============================================================================
#ifndef vtk_m_filter_SplitSharpEdges_h
#define vtk_m_filter_SplitSharpEdges_h
#include <vtkm/filter/FilterDataSetWithField.h>
#include <vtkm/worklet/SplitSharpEdges.h>
namespace vtkm
{
namespace filter
{
/// \brief Split sharp manifold edges where the feature angle between the
/// adjacent surfaces are larger than the threshold value
///
/// Split sharp manifold edges where the feature angle between the adjacent
/// surfaces are larger than the threshold value. When an edge is split, it
/// would add a new point to the coordinates and update the connectivity of
/// an adjacent surface.
/// Ex. there are two adjacent triangles(0,1,2) and (2,1,3). Edge (1,2) needs
/// to be split. Two new points 4(duplication of point 1) an 5(duplication of point 2)
/// would be added and the later triangle's connectivity would be changed
/// to (5,4,3).
/// By default, all old point's fields would be copied to the new point.
/// Use with caution.
class SplitSharpEdges : public vtkm::filter::FilterDataSetWithField<SplitSharpEdges>
{
public:
VTKM_CONT
SplitSharpEdges();
VTKM_CONT
void SetFeatureAngle(vtkm::FloatDefault value) { this->FeatureAngle = value; }
VTKM_CONT
vtkm::FloatDefault GetFeatureAngle() const { return this->FeatureAngle; }
template <typename T, typename StorageType, typename DerivedPolicy, typename DeviceAdapter>
VTKM_CONT vtkm::cont::DataSet DoExecute(const vtkm::cont::DataSet& input,
const vtkm::cont::ArrayHandle<T, StorageType>& field,
const vtkm::filter::FieldMetadata& fieldMeta,
const vtkm::filter::PolicyBase<DerivedPolicy>& policy,
DeviceAdapter tag);
//Map a new field onto the resulting dataset after running the filter
template <typename T, typename StorageType, typename DerivedPolicy, typename DeviceAdapter>
VTKM_CONT bool DoMapField(vtkm::cont::DataSet& result,
const vtkm::cont::ArrayHandle<T, StorageType>& input,
const vtkm::filter::FieldMetadata& fieldMeta,
const vtkm::filter::PolicyBase<DerivedPolicy>& policy,
DeviceAdapter tag);
private:
vtkm::FloatDefault FeatureAngle;
vtkm::worklet::SplitSharpEdges Worklet;
};
template <>
class FilterTraits<SplitSharpEdges>
{ // SplitSharpEdges filter needs cell normals to decide split.
public:
using InputFieldTypeList = vtkm::TypeListTagFieldVec3;
};
}
} // namespace vtkm::filter
#include <vtkm/filter/SplitSharpEdges.hxx>
#endif // vtk_m_filter_SplitSharpEdges_h
//============================================================================
// 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.
//
// Copyright 2014 National Technology & Engineering Solutions of Sandia, LLC (NTESS).
// Copyright 2014 UT-Battelle, LLC.
// Copyright 2014 Los Alamos National Security.
//
// Under the terms of Contract DE-NA0003525 with NTESS,
// the U.S. Government retains certain rights in this software.
//
// Under the terms of Contract DE-AC52-06NA25396 with Los Alamos National
// Laboratory (LANL), the U.S. Government retains certain rights in
// this software.
//============================================================================
#include <vtkm/cont/ArrayHandlePermutation.h>
#include <vtkm/cont/CellSetExplicit.h>
#include <vtkm/cont/CoordinateSystem.h>
#include <vtkm/cont/DynamicCellSet.h>
#include <vtkm/worklet/DispatcherMapTopology.h>
namespace vtkm
{
namespace filter
{
//-----------------------------------------------------------------------------
inline VTKM_CONT SplitSharpEdges::SplitSharpEdges()
: vtkm::filter::FilterDataSetWithField<SplitSharpEdges>()
, FeatureAngle(30.0)
{
}
//-----------------------------------------------------------------------------
template <typename T, typename StorageType, typename DerivedPolicy, typename DeviceAdapter>
inline VTKM_CONT vtkm::cont::DataSet SplitSharpEdges::DoExecute(
const vtkm::cont::DataSet& input,
const vtkm::cont::ArrayHandle<T, StorageType>& field,
const vtkm::filter::FieldMetadata& vtkmNotUsed(fieldMeta),
const vtkm::filter::PolicyBase<DerivedPolicy>& policy,
DeviceAdapter)
{
// Get the cells and coordinates of the dataset
const vtkm::cont::DynamicCellSet& cells = input.GetCellSet(this->GetActiveCellSetIndex());
vtkm::cont::ArrayHandle<vtkm::Vec<vtkm::FloatDefault, 3>> newCoords;
vtkm::cont::CellSetExplicit<> newCellset;
this->Worklet.Run(vtkm::filter::ApplyPolicy(cells, policy),
this->FeatureAngle,
field,
input.GetCoordinateSystem().GetData(),
newCoords,
newCellset,
DeviceAdapter());
vtkm::cont::DataSet output;
output.AddCellSet(newCellset);
output.AddCoordinateSystem(
vtkm::cont::CoordinateSystem(input.GetCoordinateSystem().GetName(), newCoords));
return output;
}
//-----------------------------------------------------------------------------
template <typename T, typename StorageType, typename DerivedPolicy, typename DeviceAdapter>
inline VTKM_CONT bool SplitSharpEdges::DoMapField(
vtkm::cont::DataSet& result,
const vtkm::cont::ArrayHandle<T, StorageType>& input,
const vtkm::filter::FieldMetadata& fieldMeta,
const vtkm::filter::PolicyBase<DerivedPolicy>&,
DeviceAdapter device)
{
if (fieldMeta.IsPointField())
{
// We copy the input handle to the result dataset, reusing the metadata
vtkm::cont::ArrayHandle<T> out = this->Worklet.ProcessPointField(input, device);
result.AddField(fieldMeta.AsField(out));
return true;
}
else if (fieldMeta.IsCellField())
{
result.AddField(fieldMeta.AsField(input));
return true;
}
else
{
return false;
}
}
}
}
......@@ -52,6 +52,7 @@ set(unit_tests
UnitTestPointElevationFilter.cxx
UnitTestPointTransform.cxx
UnitTestProbe.cxx
UnitTestSplitSharpEdgesFilter.cxx
UnitTestStreamlineFilter.cxx
UnitTestSurfaceNormalsFilter.cxx
UnitTestTetrahedralizeFilter.cxx
......
//============================================================================
// 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.
//
// Copyright 2017 National Technology & Engineering Solutions of Sandia, LLC (NTESS).
// Copyright 2017 UT-Battelle, LLC.
// Copyright 2017 Los Alamos National Security.
//
// Under the terms of Contract DE-NA0003525 with NTESS,
// the U.S. Government retains certain rights in this software.
//
// Under the terms of Contract DE-AC52-06NA25396 with Los Alamos National
// Laboratory (LANL), the U.S. Government retains certain rights in
// this software.
//============================================================================
#include <vtkm/filter/SplitSharpEdges.h>
#include <vtkm/filter/SurfaceNormals.h>
#include <vtkm/cont/testing/MakeTestDataSet.h>
#include <vtkm/cont/testing/Testing.h>
#include <vtkm/worklet/DispatcherMapTopology.h>
namespace
{
using NormalsArrayHandle = vtkm::cont::ArrayHandle<vtkm::Vec<vtkm::FloatDefault, 3>>;
using DeviceAdapter = VTKM_DEFAULT_DEVICE_ADAPTER_TAG;
const vtkm::Vec<vtkm::FloatDefault, 3> expectedCoords[24] = {
{ 0.0, 0.0, 0.0 }, { 1.0, 0.0, 0.0 }, { 1.0, 0.0, 1.0 }, { 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0 },
{ 1.0, 1.0, 0.0 }, { 1.0, 1.0, 1.0 }, { 0.0, 1.0, 1.0 }, { 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0 },
{ 1.0, 0.0, 0.0 }, { 1.0, 0.0, 0.0 }, { 1.0, 0.0, 1.0 }, { 1.0, 0.0, 1.0 }, { 0.0, 0.0, 1.0 },
{ 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0 }, { 0.0, 1.0, 0.0 }, { 1.0, 1.0, 0.0 }, { 1.0, 1.0, 0.0 },
{ 1.0, 1.0, 1.0 }, { 1.0, 1.0, 1.0 }, { 0.0, 1.0, 1.0 }, { 0.0, 1.0, 1.0 }
};
const std::vector<vtkm::Id> expectedConnectivityArray91{ 0, 1, 5, 4, 1, 2, 6, 5, 2, 3, 7, 6,
3, 0, 4, 7, 4, 5, 6, 7, 0, 3, 2, 1 };
const std::vector<vtkm::FloatDefault> expectedPointvar{ 10.1f, 20.1f, 30.2f, 40.2f, 50.3f, 60.3f,
70.3f, 80.3f, 10.1f, 10.1f, 20.1f, 20.1f,
30.2f, 30.2f, 40.2f, 40.2f, 50.3f, 50.3f,
60.3f, 60.3f, 70.3f, 70.3f, 80.3f, 80.3f };
vtkm::cont::DataSet Make3DExplicitSimpleCube()
{
vtkm::cont::DataSet dataSet;
vtkm::cont::DataSetBuilderExplicit dsb;
const int nVerts = 8;
const int nCells = 6;
using CoordType = vtkm::Vec<vtkm::FloatDefault, 3>;
std::vector<CoordType> coords = {
CoordType(0, 0, 0), // 0
CoordType(1, 0, 0), // 1
CoordType(1, 0, 1), // 2
CoordType(0, 0, 1), // 3
CoordType(0, 1, 0), // 4
CoordType(1, 1, 0), // 5
CoordType(1, 1, 1), // 6
CoordType(0, 1, 1) // 7
};
//Connectivity
std::vector<vtkm::UInt8> shapes;
std::vector<vtkm::IdComponent> numIndices;
for (size_t i = 0; i < 6; i++)
{
shapes.push_back(vtkm::CELL_SHAPE_QUAD);
numIndices.push_back(4);
}
std::vector<vtkm::Id> conn;
// Down face
conn.push_back(0);
conn.push_back(1);
conn.push_back(5);
conn.push_back(4);
// Right face
conn.push_back(1);
conn.push_back(2);
conn.push_back(6);
conn.push_back(5);
// Top face
conn.push_back(2);
conn.push_back(3);
conn.push_back(7);
conn.push_back(6);
// Left face
conn.push_back(3);
conn.push_back(0);
conn.push_back(4);
conn.push_back(7);
// Front face
conn.push_back(4);
conn.push_back(5);
conn.push_back(6);
conn.push_back(7);
// Back face
conn.push_back(0);
conn.push_back(3);
conn.push_back(2);
conn.push_back(1);
//Create the dataset.
dataSet = dsb.Create(coords, shapes, numIndices, conn, "coordinates", "cells");
vtkm::FloatDefault vars[nVerts] = { 10.1f, 20.1f, 30.2f, 40.2f, 50.3f, 60.3f, 70.3f, 80.3f };
vtkm::FloatDefault cellvar[nCells] = { 100.1f, 200.2f, 300.3f, 400.4f, 500.5f, 600.6f };
vtkm::cont::DataSetFieldAdd dsf;
dsf.AddPointField(dataSet, "pointvar", vars, nVerts);
dsf.AddCellField(dataSet, "cellvar", cellvar, nCells, "cells");
return dataSet;
}
void TestSplitSharpEdgesFilterSplitEveryEdge(vtkm::cont::DataSet& simpleCubeWithSN,
vtkm::filter::SplitSharpEdges& splitSharpEdgesFilter)
{
// Split every edge
vtkm::FloatDefault featureAngle = 89.0;
splitSharpEdgesFilter.SetFeatureAngle(featureAngle);
splitSharpEdgesFilter.SetActiveField("Normals", vtkm::cont::Field::Association::CELL_SET);
vtkm::cont::DataSet result = splitSharpEdgesFilter.Execute(simpleCubeWithSN);
auto newCoords = result.GetCoordinateSystem().GetData();
auto newCoordsP = newCoords.GetPortalConstControl();
vtkm::cont::ArrayHandle<vtkm::FloatDefault> newPointvarField;
result.GetField("pointvar").GetData().CopyTo(newPointvarField);
for (vtkm::IdComponent i = 0; i < newCoords.GetNumberOfValues(); i++)
{
VTKM_TEST_ASSERT(test_equal(newCoordsP.Get(i)[0], expectedCoords[i][0]),
"result value does not match expected value");
VTKM_TEST_ASSERT(test_equal(newCoordsP.Get(i)[1], expectedCoords[i][1]),
"result value does not match expected value");
VTKM_TEST_ASSERT(test_equal(newCoordsP.Get(i)[2], expectedCoords[i][2]),
"result value does not match expected value");
}
auto newPointvarFieldPortal = newPointvarField.GetPortalConstControl();
for (vtkm::IdComponent i = 0; i < newPointvarField.GetNumberOfValues(); i++)
{
VTKM_TEST_ASSERT(test_equal(newPointvarFieldPortal.Get(static_cast<vtkm::Id>(i)),
expectedPointvar[static_cast<unsigned long>(i)]),
"point field array result does not match expected value");
}
}
void TestSplitSharpEdgesFilterNoSplit(vtkm::cont::DataSet& simpleCubeWithSN,
vtkm::filter::SplitSharpEdges& splitSharpEdgesFilter)
{
// Do nothing
vtkm::FloatDefault featureAngle = 91.0;
splitSharpEdgesFilter.SetFeatureAngle(featureAngle);
splitSharpEdgesFilter.SetActiveField("Normals", vtkm::cont::Field::Association::CELL_SET);
vtkm::cont::DataSet result = splitSharpEdgesFilter.Execute(simpleCubeWithSN);
auto newCoords = result.GetCoordinateSystem().GetData();
vtkm::cont::CellSetExplicit<>& newCellset =
result.GetCellSet().Cast<vtkm::cont::CellSetExplicit<>>();
auto newCoordsP = newCoords.GetPortalConstControl();
vtkm::cont::ArrayHandle<vtkm::FloatDefault> newPointvarField;
result.GetField("pointvar").GetData().CopyTo(newPointvarField);
for (vtkm::IdComponent i = 0; i < newCoords.GetNumberOfValues(); i++)
{
VTKM_TEST_ASSERT(test_equal(newCoordsP.Get(i)[0], expectedCoords[i][0]),
"result value does not match expected value");
VTKM_TEST_ASSERT(test_equal(newCoordsP.Get(i)[1], expectedCoords[i][1]),
"result value does not match expected value");
VTKM_TEST_ASSERT(test_equal(newCoordsP.Get(i)[2], expectedCoords[i][2]),
"result value does not match expected value");
}
const auto& connectivityArray = newCellset.GetConnectivityArray(vtkm::TopologyElementTagPoint(),
vtkm::TopologyElementTagCell());
auto connectivityArrayPortal = connectivityArray.GetPortalConstControl();
for (vtkm::IdComponent i = 0; i < connectivityArray.GetNumberOfValues(); i++)
{
VTKM_TEST_ASSERT(connectivityArrayPortal.Get(static_cast<vtkm::Id>(i)) ==
expectedConnectivityArray91[static_cast<unsigned long>(i)],
"connectivity array result does not match expected value");
}
auto newPointvarFieldPortal = newPointvarField.GetPortalConstControl();
for (vtkm::IdComponent i = 0; i < newPointvarField.GetNumberOfValues(); i++)
{
VTKM_TEST_ASSERT(test_equal(newPointvarFieldPortal.Get(static_cast<vtkm::Id>(i)),
expectedPointvar[static_cast<unsigned long>(i)]),
"point field array result does not match expected value");
}
}
void TestSplitSharpEdgesFilter()
{
vtkm::cont::DataSet simpleCube = Make3DExplicitSimpleCube();
// Generate surface normal field
vtkm::filter::SurfaceNormals surfaceNormalsFilter;
surfaceNormalsFilter.SetGenerateCellNormals(true);
vtkm::cont::DataSet simpleCubeWithSN = surfaceNormalsFilter.Execute(simpleCube);
VTKM_TEST_ASSERT(simpleCubeWithSN.HasField("Normals", vtkm::cont::Field::Association::CELL_SET),
"Cell normals missing.");
VTKM_TEST_ASSERT(simpleCubeWithSN.HasField("pointvar", vtkm::cont::Field::Association::POINTS),
"point field pointvar missing.");
vtkm::filter::SplitSharpEdges splitSharpEdgesFilter;
TestSplitSharpEdgesFilterSplitEveryEdge(simpleCubeWithSN, splitSharpEdgesFilter);
TestSplitSharpEdgesFilterNoSplit(simpleCubeWithSN, splitSharpEdgesFilter);
}
} // anonymous namespace
int UnitTestSplitSharpEdgesFilter(int, char* [])
{
return vtkm::cont::testing::Testing::Run(TestSplitSharpEdgesFilter);
}
......@@ -67,6 +67,7 @@ set(headers
ScatterIdentity.h
ScatterPermutation.h
ScatterUniform.h
SplitSharpEdges.h
StableSortIndices.h
StreamLineUniformGrid.h
SurfaceNormals.h
......
This diff is collapsed.
......@@ -62,6 +62,7 @@ set(unit_tests
UnitTestScatterCounting.cxx
UnitTestScatterPermutation.cxx
UnitTestSplatKernels.cxx
UnitTestSplitSharpEdges.cxx
UnitTestStreamingSine.cxx
UnitTestStreamLineUniformGrid.cxx
UnitTestSurfaceNormals.cxx
......
//============================================================================
// 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.
//
// Copyright 2017 National Technology & Engineering Solutions of Sandia, LLC (NTESS).
// Copyright 2017 UT-Battelle, LLC.
// Copyright 2017 Los Alamos National Security.
//
// Under the terms of Contract DE-NA0003525 with NTESS,
// the U.S. Government retains certain rights in this software.
//
// Under the terms of Contract DE-AC52-06NA25396 with Los Alamos National
// Laboratory (LANL), the U.S. Government retains certain rights in
// this software.
//============================================================================
#include <vtkm/worklet/SplitSharpEdges.h>
#include <vtkm/worklet/SurfaceNormals.h>
#include <vtkm/cont/testing/MakeTestDataSet.h>
#include <vtkm/cont/testing/Testing.h>
#include <vtkm/worklet/DispatcherMapTopology.h>
namespace
{
using NormalsArrayHandle = vtkm::cont::ArrayHandle<vtkm::Vec<