1 // Copyright Contributors to the OpenVDB Project
2 // SPDX-License-Identifier: MPL-2.0
3 
4 #include <openvdb/Exceptions.h>
5 #include <openvdb/openvdb.h>
6 #include <openvdb/Types.h>
7 #include <openvdb/util/Name.h>
8 #include <openvdb/math/Transform.h>
9 #include <openvdb/Grid.h>
10 #include <openvdb/tree/Tree.h>
11 #include <openvdb/util/CpuTimer.h>
12 #include "gtest/gtest.h"
13 #include <iostream>
14 #include <memory> // for std::make_unique
15 
16 #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \
17     EXPECT_NEAR((expected), (actual), /*tolerance=*/0.0);
18 
19 class TestGrid: public ::testing::Test
20 {
21 };
22 
23 
24 ////////////////////////////////////////
25 
26 
27 class ProxyTree: public openvdb::TreeBase
28 {
29 public:
30     using ValueType = int;
31     using BuildType = int;
32     using LeafNodeType = void;
33     using ValueAllCIter = void;
34     using ValueAllIter = void;
35     using ValueOffCIter = void;
36     using ValueOffIter = void;
37     using ValueOnCIter = void;
38     using ValueOnIter = void;
39     using TreeBasePtr = openvdb::TreeBase::Ptr;
40     using Ptr = openvdb::SharedPtr<ProxyTree>;
41     using ConstPtr = openvdb::SharedPtr<const ProxyTree>;
42 
43     static const openvdb::Index DEPTH;
44     static const ValueType backg;
45 
ProxyTree()46     ProxyTree() {}
ProxyTree(const ValueType &)47     ProxyTree(const ValueType&) {}
48     ProxyTree(const ProxyTree&) = default;
49     ~ProxyTree() override = default;
50 
treeType()51     static const openvdb::Name& treeType() { static const openvdb::Name s("proxy"); return s; }
type() const52     const openvdb::Name& type() const override { return treeType(); }
valueType() const53     openvdb::Name valueType() const override { return "proxy"; }
background() const54     const ValueType& background() const { return backg; }
55 
copy() const56     TreeBasePtr copy() const override { return TreeBasePtr(new ProxyTree(*this)); }
57 
readTopology(std::istream & is,bool=false)58     void readTopology(std::istream& is, bool = false) override { is.seekg(0, std::ios::beg); }
writeTopology(std::ostream & os,bool=false) const59     void writeTopology(std::ostream& os, bool = false) const override { os.seekp(0); }
60 
readBuffers(std::istream & is,const openvdb::CoordBBox &,bool=false)61     void readBuffers(std::istream& is,
62         const openvdb::CoordBBox&, bool /*saveFloatAsHalf*/=false) override { is.seekg(0); }
readNonresidentBuffers() const63     void readNonresidentBuffers() const override {}
readBuffers(std::istream & is,bool=false)64     void readBuffers(std::istream& is, bool /*saveFloatAsHalf*/=false) override { is.seekg(0); }
writeBuffers(std::ostream & os,bool=false) const65     void writeBuffers(std::ostream& os, bool /*saveFloatAsHalf*/=false) const override
66         { os.seekp(0, std::ios::beg); }
67 
empty() const68     bool empty() const { return true; }
clear()69     void clear() {}
prune(const ValueType &=0)70     void prune(const ValueType& = 0) {}
clip(const openvdb::CoordBBox &)71     void clip(const openvdb::CoordBBox&) {}
clipUnallocatedNodes()72     void clipUnallocatedNodes() override {}
unallocatedLeafCount() const73     openvdb::Index32 unallocatedLeafCount() const override { return 0; }
74 
getIndexRange(openvdb::CoordBBox &) const75     void getIndexRange(openvdb::CoordBBox&) const override {}
evalLeafBoundingBox(openvdb::CoordBBox & bbox) const76     bool evalLeafBoundingBox(openvdb::CoordBBox& bbox) const override
77         { bbox.min() = bbox.max() = openvdb::Coord(0, 0, 0); return false; }
evalActiveVoxelBoundingBox(openvdb::CoordBBox & bbox) const78     bool evalActiveVoxelBoundingBox(openvdb::CoordBBox& bbox) const override
79         { bbox.min() = bbox.max() = openvdb::Coord(0, 0, 0); return false; }
evalActiveVoxelDim(openvdb::Coord & dim) const80     bool evalActiveVoxelDim(openvdb::Coord& dim) const override
81         { dim = openvdb::Coord(0, 0, 0); return false; }
evalLeafDim(openvdb::Coord & dim) const82     bool evalLeafDim(openvdb::Coord& dim) const override
83         { dim = openvdb::Coord(0, 0, 0); return false; }
84 
treeDepth() const85     openvdb::Index treeDepth() const override { return 0; }
leafCount() const86     openvdb::Index leafCount() const override { return 0; }
87 #if OPENVDB_ABI_VERSION_NUMBER >= 7
nodeCount() const88     std::vector<openvdb::Index32> nodeCount() const override
89         { return std::vector<openvdb::Index32>(DEPTH, 0); }
90 #endif
nonLeafCount() const91     openvdb::Index nonLeafCount() const override { return 0; }
activeVoxelCount() const92     openvdb::Index64 activeVoxelCount() const override { return 0UL; }
inactiveVoxelCount() const93     openvdb::Index64 inactiveVoxelCount() const override { return 0UL; }
activeLeafVoxelCount() const94     openvdb::Index64 activeLeafVoxelCount() const override { return 0UL; }
inactiveLeafVoxelCount() const95     openvdb::Index64 inactiveLeafVoxelCount() const override { return 0UL; }
activeTileCount() const96     openvdb::Index64 activeTileCount() const override { return 0UL; }
97 };
98 
99 const openvdb::Index ProxyTree::DEPTH = 0;
100 const ProxyTree::ValueType ProxyTree::backg = 0;
101 
102 using ProxyGrid = openvdb::Grid<ProxyTree>;
103 
104 
105 ////////////////////////////////////////
106 
TEST_F(TestGrid,testGridRegistry)107 TEST_F(TestGrid, testGridRegistry)
108 {
109     using namespace openvdb::tree;
110 
111     using TreeType = Tree<RootNode<InternalNode<LeafNode<float, 3>, 2> > >;
112     using GridType = openvdb::Grid<TreeType>;
113 
114     openvdb::GridBase::clearRegistry();
115 
116     EXPECT_TRUE(!GridType::isRegistered());
117     GridType::registerGrid();
118     EXPECT_TRUE(GridType::isRegistered());
119     EXPECT_THROW(GridType::registerGrid(), openvdb::KeyError);
120     GridType::unregisterGrid();
121     EXPECT_TRUE(!GridType::isRegistered());
122     EXPECT_NO_THROW(GridType::unregisterGrid());
123     EXPECT_TRUE(!GridType::isRegistered());
124     EXPECT_NO_THROW(GridType::registerGrid());
125     EXPECT_TRUE(GridType::isRegistered());
126 
127     openvdb::GridBase::clearRegistry();
128 }
129 
130 
TEST_F(TestGrid,testConstPtr)131 TEST_F(TestGrid, testConstPtr)
132 {
133     using namespace openvdb;
134 
135     GridBase::ConstPtr constgrid = ProxyGrid::create();
136 
137     EXPECT_EQ(Name("proxy"), constgrid->type());
138 }
139 
140 
TEST_F(TestGrid,testGetGrid)141 TEST_F(TestGrid, testGetGrid)
142 {
143     using namespace openvdb;
144 
145     GridBase::Ptr grid = FloatGrid::create(/*bg=*/0.0);
146     GridBase::ConstPtr constGrid = grid;
147 
148     EXPECT_TRUE(grid->baseTreePtr());
149 
150     EXPECT_TRUE(!gridPtrCast<DoubleGrid>(grid));
151     EXPECT_TRUE(!gridPtrCast<DoubleGrid>(grid));
152 
153     EXPECT_TRUE(gridConstPtrCast<FloatGrid>(constGrid));
154     EXPECT_TRUE(!gridConstPtrCast<DoubleGrid>(constGrid));
155 }
156 
157 
TEST_F(TestGrid,testIsType)158 TEST_F(TestGrid, testIsType)
159 {
160     using namespace openvdb;
161 
162     GridBase::Ptr grid = FloatGrid::create();
163     EXPECT_TRUE(grid->isType<FloatGrid>());
164     EXPECT_TRUE(!grid->isType<DoubleGrid>());
165 }
166 
167 
TEST_F(TestGrid,testIsTreeUnique)168 TEST_F(TestGrid, testIsTreeUnique)
169 {
170     using namespace openvdb;
171 
172     FloatGrid::Ptr grid = FloatGrid::create();
173     EXPECT_TRUE(grid->isTreeUnique());
174 
175     // a shallow copy shares the same tree
176     FloatGrid::Ptr grid2 = grid->copy();
177     EXPECT_TRUE(!grid->isTreeUnique());
178     EXPECT_TRUE(!grid2->isTreeUnique());
179 
180     // cleanup the shallow copy
181     grid2.reset();
182     EXPECT_TRUE(grid->isTreeUnique());
183 
184     // copy with new tree
185     GridBase::Ptr grid3 = grid->copyGridWithNewTree();
186     EXPECT_TRUE(grid->isTreeUnique());
187 
188 #if OPENVDB_ABI_VERSION_NUMBER >= 8
189     // shallow copy using GridBase
190     GridBase::Ptr grid4 = grid->copyGrid();
191     EXPECT_TRUE(!grid4->isTreeUnique());
192 
193     // copy with new tree using GridBase
194     GridBase::Ptr grid5 = grid->copyGridWithNewTree();
195     EXPECT_TRUE(grid5->isTreeUnique());
196 #endif
197 }
198 
199 
TEST_F(TestGrid,testTransform)200 TEST_F(TestGrid, testTransform)
201 {
202     ProxyGrid grid;
203 
204     // Verify that the grid has a valid default transform.
205     EXPECT_TRUE(grid.transformPtr());
206 
207     // Verify that a null transform pointer is not allowed.
208     EXPECT_THROW(grid.setTransform(openvdb::math::Transform::Ptr()),
209         openvdb::ValueError);
210 
211     grid.setTransform(openvdb::math::Transform::createLinearTransform());
212 
213     EXPECT_TRUE(grid.transformPtr());
214 
215     // Verify that calling Transform-related Grid methods (Grid::voxelSize(), etc.)
216     // is the same as calling those methods on the Transform.
217 
218     EXPECT_TRUE(grid.transform().voxelSize().eq(grid.voxelSize()));
219     EXPECT_TRUE(grid.transform().voxelSize(openvdb::Vec3d(0.1, 0.2, 0.3)).eq(
220         grid.voxelSize(openvdb::Vec3d(0.1, 0.2, 0.3))));
221 
222     EXPECT_TRUE(grid.transform().indexToWorld(openvdb::Vec3d(0.1, 0.2, 0.3)).eq(
223         grid.indexToWorld(openvdb::Vec3d(0.1, 0.2, 0.3))));
224     EXPECT_TRUE(grid.transform().indexToWorld(openvdb::Coord(1, 2, 3)).eq(
225         grid.indexToWorld(openvdb::Coord(1, 2, 3))));
226     EXPECT_TRUE(grid.transform().worldToIndex(openvdb::Vec3d(0.1, 0.2, 0.3)).eq(
227         grid.worldToIndex(openvdb::Vec3d(0.1, 0.2, 0.3))));
228 }
229 
230 
TEST_F(TestGrid,testCopyGrid)231 TEST_F(TestGrid, testCopyGrid)
232 {
233     using namespace openvdb;
234 
235     // set up a grid
236     const float fillValue1=5.0f;
237     FloatGrid::Ptr grid1 = createGrid<FloatGrid>(/*bg=*/fillValue1);
238     FloatTree& tree1 = grid1->tree();
239     tree1.setValue(Coord(-10,40,845), 3.456f);
240     tree1.setValue(Coord(1,-50,-8), 1.0f);
241 
242     // create a new grid, copying the first grid
243     GridBase::Ptr grid2 = grid1->deepCopy();
244 
245     // cast down to the concrete type to query values
246     FloatTree& tree2 = gridPtrCast<FloatGrid>(grid2)->tree();
247 
248     // compare topology
249     EXPECT_TRUE(tree1.hasSameTopology(tree2));
250     EXPECT_TRUE(tree2.hasSameTopology(tree1));
251 
252     // trees should be equal
253     ASSERT_DOUBLES_EXACTLY_EQUAL(fillValue1, tree2.getValue(Coord(1,2,3)));
254     ASSERT_DOUBLES_EXACTLY_EQUAL(3.456f, tree2.getValue(Coord(-10,40,845)));
255     ASSERT_DOUBLES_EXACTLY_EQUAL(1.0f, tree2.getValue(Coord(1,-50,-8)));
256 
257     // change 1 value in tree2
258     Coord changeCoord(1, -500, -8);
259     tree2.setValue(changeCoord, 1.0f);
260 
261     // topology should no longer match
262     EXPECT_TRUE(!tree1.hasSameTopology(tree2));
263     EXPECT_TRUE(!tree2.hasSameTopology(tree1));
264 
265     // query changed value and make sure it's different between trees
266     ASSERT_DOUBLES_EXACTLY_EQUAL(fillValue1, tree1.getValue(changeCoord));
267     ASSERT_DOUBLES_EXACTLY_EQUAL(1.0f, tree2.getValue(changeCoord));
268 
269 #if OPENVDB_ABI_VERSION_NUMBER >= 7
270     // shallow-copy a const grid but supply a new transform and meta map
271     EXPECT_EQ(1.0, grid1->transform().voxelSize().x());
272     EXPECT_EQ(size_t(0), grid1->metaCount());
273     EXPECT_EQ(Index(2), grid1->tree().leafCount());
274 
275     math::Transform::Ptr xform(math::Transform::createLinearTransform(/*voxelSize=*/0.25));
276     MetaMap meta;
277     meta.insertMeta("test", Int32Metadata(4));
278 
279     FloatGrid::ConstPtr constGrid1 = ConstPtrCast<const FloatGrid>(grid1);
280 
281     GridBase::ConstPtr grid3 = constGrid1->copyGridReplacingMetadataAndTransform(meta, xform);
282     const FloatTree& tree3 = gridConstPtrCast<FloatGrid>(grid3)->tree();
283 
284     EXPECT_EQ(0.25, grid3->transform().voxelSize().x());
285     EXPECT_EQ(size_t(1), grid3->metaCount());
286     EXPECT_EQ(Index(2), tree3.leafCount());
287     EXPECT_EQ(long(3), constGrid1->constTreePtr().use_count());
288 #endif
289 }
290 
291 
TEST_F(TestGrid,testValueConversion)292 TEST_F(TestGrid, testValueConversion)
293 {
294     using namespace openvdb;
295 
296     const Coord c0(-10, 40, 845), c1(1, -50, -8), c2(1, 2, 3);
297     const float fval0 = 3.25f, fval1 = 1.0f, fbkgd = 5.0f;
298 
299     // Create a FloatGrid.
300     FloatGrid fgrid(fbkgd);
301     FloatTree& ftree = fgrid.tree();
302     ftree.setValue(c0, fval0);
303     ftree.setValue(c1, fval1);
304 
305     // Copy the FloatGrid to a DoubleGrid.
306     DoubleGrid dgrid(fgrid);
307     DoubleTree& dtree = dgrid.tree();
308     // Compare topology.
309     EXPECT_TRUE(dtree.hasSameTopology(ftree));
310     EXPECT_TRUE(ftree.hasSameTopology(dtree));
311     // Compare values.
312     ASSERT_DOUBLES_EXACTLY_EQUAL(double(fbkgd), dtree.getValue(c2));
313     ASSERT_DOUBLES_EXACTLY_EQUAL(double(fval0), dtree.getValue(c0));
314     ASSERT_DOUBLES_EXACTLY_EQUAL(double(fval1), dtree.getValue(c1));
315 
316     // Copy the FloatGrid to a BoolGrid.
317     BoolGrid bgrid(fgrid);
318     BoolTree& btree = bgrid.tree();
319     // Compare topology.
320     EXPECT_TRUE(btree.hasSameTopology(ftree));
321     EXPECT_TRUE(ftree.hasSameTopology(btree));
322     // Compare values.
323     EXPECT_EQ(bool(fbkgd), btree.getValue(c2));
324     EXPECT_EQ(bool(fval0), btree.getValue(c0));
325     EXPECT_EQ(bool(fval1), btree.getValue(c1));
326 
327     // Copy the FloatGrid to a Vec3SGrid.
328     Vec3SGrid vgrid(fgrid);
329     Vec3STree& vtree = vgrid.tree();
330     // Compare topology.
331     EXPECT_TRUE(vtree.hasSameTopology(ftree));
332     EXPECT_TRUE(ftree.hasSameTopology(vtree));
333     // Compare values.
334     EXPECT_EQ(Vec3s(fbkgd), vtree.getValue(c2));
335     EXPECT_EQ(Vec3s(fval0), vtree.getValue(c0));
336     EXPECT_EQ(Vec3s(fval1), vtree.getValue(c1));
337 
338     // Verify that a Vec3SGrid can't be copied to an Int32Grid
339     // (because an Int32 can't be constructed from a Vec3S).
340     EXPECT_THROW(Int32Grid igrid2(vgrid), openvdb::TypeError);
341 
342     // Verify that a grid can't be converted to another type with a different
343     // tree configuration.
344     using DTree23 = tree::Tree3<double, 2, 3>::Type;
345     using DGrid23 = Grid<DTree23>;
346     EXPECT_THROW(DGrid23 d23grid(fgrid), openvdb::TypeError);
347 }
348 
349 
350 ////////////////////////////////////////
351 
352 
353 template<typename GridT>
354 void
validateClippedGrid(const GridT & clipped,const typename GridT::ValueType & fg)355 validateClippedGrid(const GridT& clipped, const typename GridT::ValueType& fg)
356 {
357     using namespace openvdb;
358 
359     using ValueT = typename GridT::ValueType;
360 
361     const CoordBBox bbox = clipped.evalActiveVoxelBoundingBox();
362     EXPECT_EQ(4, bbox.min().x());
363     EXPECT_EQ(4, bbox.min().y());
364     EXPECT_EQ(-6, bbox.min().z());
365     EXPECT_EQ(4, bbox.max().x());
366     EXPECT_EQ(4, bbox.max().y());
367     EXPECT_EQ(6, bbox.max().z());
368     EXPECT_EQ(6 + 6 + 1, int(clipped.activeVoxelCount()));
369     EXPECT_EQ(2, int(clipped.constTree().leafCount()));
370 
371     typename GridT::ConstAccessor acc = clipped.getConstAccessor();
372     const ValueT bg = clipped.background();
373     Coord xyz;
374     int &x = xyz[0], &y = xyz[1], &z = xyz[2];
375     for (x = -10; x <= 10; ++x) {
376         for (y = -10; y <= 10; ++y) {
377             for (z = -10; z <= 10; ++z) {
378                 if (x == 4 && y == 4 && z >= -6 && z <= 6) {
379                     EXPECT_EQ(fg, acc.getValue(Coord(4, 4, z)));
380                 } else {
381                     EXPECT_EQ(bg, acc.getValue(Coord(x, y, z)));
382                 }
383             }
384         }
385     }
386 }
387 
388 
389 // See also TestTools::testClipping()
TEST_F(TestGrid,testClipping)390 TEST_F(TestGrid, testClipping)
391 {
392     using namespace openvdb;
393 
394     const BBoxd clipBox(Vec3d(4.0, 4.0, -6.0), Vec3d(4.9, 4.9, 6.0));
395 
396     {
397         const float fg = 5.f;
398         FloatGrid cube(0.f);
399         cube.fill(CoordBBox(Coord(-10), Coord(10)), /*value=*/fg, /*active=*/true);
400         cube.clipGrid(clipBox);
401         validateClippedGrid(cube, fg);
402     }
403     {
404         const bool fg = true;
405         BoolGrid cube(false);
406         cube.fill(CoordBBox(Coord(-10), Coord(10)), /*value=*/fg, /*active=*/true);
407         cube.clipGrid(clipBox);
408         validateClippedGrid(cube, fg);
409     }
410     {
411         const Vec3s fg(1.f, -2.f, 3.f);
412         Vec3SGrid cube(Vec3s(0.f));
413         cube.fill(CoordBBox(Coord(-10), Coord(10)), /*value=*/fg, /*active=*/true);
414         cube.clipGrid(clipBox);
415         validateClippedGrid(cube, fg);
416     }
417     /*
418     {// Benchmark multi-threaded copy construction
419         openvdb::util::CpuTimer timer;
420         openvdb::initialize();
421         openvdb::io::File file("/usr/pic1/Data/OpenVDB/LevelSetModels/crawler.vdb");
422         file.open();
423         openvdb::GridBase::Ptr baseGrid = file.readGrid("ls_crawler");
424         file.close();
425         openvdb::FloatGrid::Ptr grid = openvdb::gridPtrCast<openvdb::FloatGrid>(baseGrid);
426         //grid->tree().print();
427         timer.start("\nCopy construction");
428         openvdb::FloatTree fTree(grid->tree());
429         timer.stop();
430 
431         timer.start("\nBoolean topology copy construction");
432         openvdb::BoolTree bTree(grid->tree(), false, openvdb::TopologyCopy());
433         timer.stop();
434 
435         timer.start("\nBoolean topology union");
436         bTree.topologyUnion(fTree);
437         timer.stop();
438         //bTree.print();
439     }
440     */
441 }
442 
443 
444 ////////////////////////////////////////
445 
446 
447 namespace {
448 
449 struct GridOp
450 {
451     bool isConst = false;
operator ()__anon7337ad640111::GridOp452     template<typename GridT> void operator()(const GridT&) { isConst = true; }
operator ()__anon7337ad640111::GridOp453     template<typename GridT> void operator()(GridT&) { isConst = false; }
454 };
455 
456 } // anonymous namespace
457 
458 
TEST_F(TestGrid,testApply)459 TEST_F(TestGrid, testApply)
460 {
461     using namespace openvdb;
462 
463     const GridBase::Ptr
464         boolGrid = BoolGrid::create(),
465         floatGrid = FloatGrid::create(),
466         doubleGrid = DoubleGrid::create(),
467         intGrid = Int32Grid::create();
468 
469     const GridBase::ConstPtr
470         boolCGrid = BoolGrid::create(),
471         floatCGrid = FloatGrid::create(),
472         doubleCGrid = DoubleGrid::create(),
473         intCGrid = Int32Grid::create();
474 
475     {
476         using AllowedGridTypes = TypeList<>;
477 
478         // Verify that the functor is not applied to any of the grids.
479         GridOp op;
480         EXPECT_TRUE(!boolGrid->apply<AllowedGridTypes>(op));
481         EXPECT_TRUE(!boolCGrid->apply<AllowedGridTypes>(op));
482         EXPECT_TRUE(!floatGrid->apply<AllowedGridTypes>(op));
483         EXPECT_TRUE(!floatCGrid->apply<AllowedGridTypes>(op));
484         EXPECT_TRUE(!doubleGrid->apply<AllowedGridTypes>(op));
485         EXPECT_TRUE(!doubleCGrid->apply<AllowedGridTypes>(op));
486         EXPECT_TRUE(!intGrid->apply<AllowedGridTypes>(op));
487         EXPECT_TRUE(!intCGrid->apply<AllowedGridTypes>(op));
488     }
489     {
490         using AllowedGridTypes = TypeList<FloatGrid, FloatGrid, DoubleGrid>;
491 
492         // Verify that the functor is applied only to grids of the allowed types
493         // and that their constness is respected.
494         GridOp op;
495         EXPECT_TRUE(!boolGrid->apply<AllowedGridTypes>(op));
496         EXPECT_TRUE(!intGrid->apply<AllowedGridTypes>(op));
497         EXPECT_TRUE(floatGrid->apply<AllowedGridTypes>(op));  EXPECT_TRUE(!op.isConst);
498         EXPECT_TRUE(doubleGrid->apply<AllowedGridTypes>(op)); EXPECT_TRUE(!op.isConst);
499 
500         EXPECT_TRUE(!boolCGrid->apply<AllowedGridTypes>(op));
501         EXPECT_TRUE(!intCGrid->apply<AllowedGridTypes>(op));
502         EXPECT_TRUE(floatCGrid->apply<AllowedGridTypes>(op));  EXPECT_TRUE(op.isConst);
503         EXPECT_TRUE(doubleCGrid->apply<AllowedGridTypes>(op)); EXPECT_TRUE(op.isConst);
504     }
505     {
506         using AllowedGridTypes = TypeList<FloatGrid, DoubleGrid>;
507 
508         // Verify that rvalue functors are supported.
509         int n = 0;
510         EXPECT_TRUE(  !boolGrid->apply<AllowedGridTypes>([&n](GridBase&) { ++n; }));
511         EXPECT_TRUE(   !intGrid->apply<AllowedGridTypes>([&n](GridBase&) { ++n; }));
512         EXPECT_TRUE(  floatGrid->apply<AllowedGridTypes>([&n](GridBase&) { ++n; }));
513         EXPECT_TRUE( doubleGrid->apply<AllowedGridTypes>([&n](GridBase&) { ++n; }));
514         EXPECT_TRUE( !boolCGrid->apply<AllowedGridTypes>([&n](const GridBase&) { ++n; }));
515         EXPECT_TRUE(  !intCGrid->apply<AllowedGridTypes>([&n](const GridBase&) { ++n; }));
516         EXPECT_TRUE( floatCGrid->apply<AllowedGridTypes>([&n](const GridBase&) { ++n; }));
517         EXPECT_TRUE(doubleCGrid->apply<AllowedGridTypes>([&n](const GridBase&) { ++n; }));
518         EXPECT_EQ(4, n);
519     }
520 }
521