Browse Source

SceneTools: add filterObjects().

pull/620/head
Vladimír Vondruš 3 years ago
parent
commit
085ae0ee8d
  1. 130
      src/Magnum/SceneTools/Filter.cpp
  2. 23
      src/Magnum/SceneTools/Filter.h
  3. 321
      src/Magnum/SceneTools/Test/FilterTest.cpp

130
src/Magnum/SceneTools/Filter.cpp

@ -26,6 +26,7 @@
#include "Filter.h"
#include <map>
#include <Corrade/Containers/ArrayTuple.h>
#include <Corrade/Containers/BitArray.h>
#include <Corrade/Containers/BitArrayView.h>
#include <Corrade/Containers/Optional.h>
@ -259,4 +260,133 @@ Trade::SceneData filterFieldEntries(const Trade::SceneData& scene, const std::in
return filterFieldEntries(scene, Containers::arrayView(entriesToKeep));
}
namespace {
template<class T> std::size_t filterObjectsImplementation(const Trade::SceneData& scene, const Containers::ArrayView<Containers::Pair<UnsignedInt, Containers::BitArrayView>> fieldStorage, const Containers::MutableBitArrayView maskStorage, const Containers::BitArrayView objects, std::map<std::tuple<const void*, std::size_t, std::ptrdiff_t>, Containers::Optional<UnsignedInt>>& uniqueMappings) {
std::size_t fieldOffset = 0;
std::size_t maskOffset = 0;
for(UnsignedInt fieldId = 0; fieldId != scene.fieldCount(); ++fieldId) {
/* Skip empty fields as there's nothing to do for them and they don't
even have an entry in the uniqueMappings map */
if(!scene.fieldSize(fieldId))
continue;
const Containers::StridedArrayView1D<const T> mapping = scene.mapping<T>(fieldId);
/* Shared mappings need to stay shared, thus filterFieldEntries() needs
to get the exact same mask for such fields -- for implementation
simplicity not just the bit values but the actual view */
Containers::Optional<UnsignedInt>& sharedMapping = uniqueMappings.at(std::make_tuple(mapping.data(), mapping.size(), mapping.stride()));
/* If a mask was already calculated for this mapping, reuse the view */
if(sharedMapping) {
/* If the field wasn't filtered in any way, it wasn't added to the
list, which is indicated by ~UnsignedInt{}. Do nothing in that
case. */
if(*sharedMapping != ~UnsignedInt{})
fieldStorage[fieldOffset++] = {fieldId, fieldStorage[*sharedMapping].second()};
/* If not, calculate the mask and remember it for potential other
fields that share the same mapping view */
} else {
const Containers::MutableBitArrayView mask = maskStorage.sliceSize(maskOffset, mapping.size());
bool anyFiltered = false;
for(std::size_t i = 0; i != mapping.size(); ++i) {
/** @todo ugh! mask.set(i, objects[mapping[i]]) and then .all()
once it's implemented (needs BMI variants similarly to
count()) */
if(objects[mapping[i]])
mask.set(i);
else {
anyFiltered = true;
mask.reset(i);
}
}
/* Only add the field to the list if it's not all 1s */
if(anyFiltered) {
sharedMapping = fieldOffset;
fieldStorage[fieldOffset++] = {fieldId, mask};
/* Not bothering with rounding this to whole bytes as
Utility::copyMasked() has to special-case the begin/end
anyway */
maskOffset += mask.size();
} else {
sharedMapping = ~UnsignedInt{};
}
}
}
CORRADE_INTERNAL_ASSERT(fieldOffset <= fieldStorage.size());
CORRADE_INTERNAL_ASSERT(maskOffset <= maskStorage.size());
return fieldOffset;
}
}
Trade::SceneData filterObjects(const Trade::SceneData& scene, const Containers::BitArrayView objects) {
CORRADE_ASSERT(objects.size() == scene.mappingBound(),
"SceneTools::filterObjects(): expected" << scene.mappingBound() << "bits but got" << objects.size(), (Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}}));
/** @todo while a BitArrayView is certainly faster for lookup than an
unordered list of IDs, it might become rather problematic in cases
where the mapping bound is sparse and *really huge* (i.e., storing
pointers) -- then there either needs to be an overload that takes an
`ArrayView<const UnsignedLong>` and does some less ideal lookup, or a
`packObjects()` tool that makes the object numbering contiguous for
this API to be usable, storing also mapping back to the original ID in
the scene, and an `unpackObjects()` that restores the original IDs */
/* Count the total count of bits possibly needed */
std::size_t bitCount = 0;
for(UnsignedInt i = 0; i != scene.fieldCount(); ++i)
bitCount += scene.fieldSize(i);
/* Allocate scratch memory for all the bits and field references */
Containers::ArrayView<Containers::Pair<UnsignedInt, Containers::BitArrayView>> fieldStorage;
Containers::MutableBitArrayView maskStorage;
Containers::ArrayTuple storage{
{NoInit, scene.fieldCount(), fieldStorage},
{NoInit, bitCount, maskStorage}
};
/* Collect a map of unique mappings. The value is a placeholder where
filterObjectsImplementation() will subsequently record a reference to a
BitArrayView that should be used for all fields. */
std::map<std::tuple<const void*, std::size_t, std::ptrdiff_t>, Containers::Optional<UnsignedInt>> uniqueMappings;
for(UnsignedInt i = 0; i != scene.fieldCount(); ++i) {
/* Skip empty fields as those make no sense to include for sharing */
if(!scene.fieldSize(i))
continue;
const Containers::StridedArrayView2D<const char> mapping = scene.mapping(i);
uniqueMappings.emplace(std::make_tuple(mapping.data(), mapping.size()[0], mapping.stride()[0]), Containers::NullOpt);
}
/* Delegate to a concrete filtering implementation based on used mapping
type. Returns the prefix of fieldStorage that got filled, with fields
that didn't need to be changed omitted. */
std::size_t fieldCount = ~std::size_t{};
switch(scene.mappingType()) {
case Trade::SceneMappingType::UnsignedByte:
fieldCount = filterObjectsImplementation<UnsignedByte>(scene, fieldStorage, maskStorage, objects, uniqueMappings);
break;
case Trade::SceneMappingType::UnsignedShort:
fieldCount = filterObjectsImplementation<UnsignedShort>(scene, fieldStorage, maskStorage, objects, uniqueMappings);
break;
case Trade::SceneMappingType::UnsignedInt:
fieldCount = filterObjectsImplementation<UnsignedInt>(scene, fieldStorage, maskStorage, objects, uniqueMappings);
break;
case Trade::SceneMappingType::UnsignedLong:
fieldCount = filterObjectsImplementation<UnsignedLong>(scene, fieldStorage, maskStorage, objects, uniqueMappings);
break;
}
CORRADE_INTERNAL_ASSERT(fieldCount != ~std::size_t{});
/* Delegate the rest to the low-level field entry filtering API */
return filterFieldEntries(scene, fieldStorage.prefix(fieldCount));
}
}}

23
src/Magnum/SceneTools/Filter.h

@ -157,6 +157,29 @@ MAGNUM_SCENETOOLS_EXPORT Trade::SceneData filterFieldEntries(const Trade::SceneD
*/
MAGNUM_SCENETOOLS_EXPORT Trade::SceneData filterFieldEntries(const Trade::SceneData& scene, std::initializer_list<Containers::Pair<Trade::SceneField, Containers::BitArrayView>> entriesToKeep);
/**
@brief Filter objects in a scene
@m_since_latest
Returns a copy of @p scene containing the same fields but only with entries
mapped to objects for which the corresponding bit in @p objectsToKeep is set.
The size of @p objectsToKeep is expected to be equal to
@ref Trade::SceneData::mappingBound().
Fields that don't contain any entries mapped to filtered-out objects are passed
through unchanged. The data filtering is performed using
@ref filterFieldEntries() which then delegates to @ref combineFields() for
repacking the data, see their documentation for more information.
Note that this function performs only filtering of the data, it doesn't change
the data in any other way. If there are references to the removed objects from
other fields such as @ref Trade::SceneField::Parent, it's the responsibility of
the caller to deal with them either before or after calling this API, otherwise
the returned data may end up being unusable.
@experimental
*/
MAGNUM_SCENETOOLS_EXPORT Trade::SceneData filterObjects(const Trade::SceneData& scene, Containers::BitArrayView objectsToKeep);
}}
#endif

321
src/Magnum/SceneTools/Test/FilterTest.cpp

@ -59,6 +59,12 @@ struct FilterTest: TestSuite::Tester {
void fieldEntriesSharedMapping();
void fieldEntriesSharedMappingInvalid();
template<class T> void objects();
void objectsUnchangedFields();
void objectsSharedMapping();
void objectsSharedMappingAllRemoved();
void objectsWrongBitCount();
};
using namespace Math::Literals;
@ -91,7 +97,16 @@ FilterTest::FilterTest() {
&FilterTest::fieldEntriesBitField,
&FilterTest::fieldEntriesSharedMapping,
&FilterTest::fieldEntriesSharedMappingInvalid});
&FilterTest::fieldEntriesSharedMappingInvalid,
&FilterTest::objects<UnsignedByte>,
&FilterTest::objects<UnsignedShort>,
&FilterTest::objects<UnsignedInt>,
&FilterTest::objects<UnsignedLong>,
&FilterTest::objectsUnchangedFields,
&FilterTest::objectsSharedMapping,
&FilterTest::objectsSharedMappingAllRemoved,
&FilterTest::objectsWrongBitCount});
}
void FilterTest::fields() {
@ -760,6 +775,310 @@ void FilterTest::fieldEntriesSharedMappingInvalid() {
"SceneTools::filterFieldEntries(): field Trade::SceneField::Custom(1) shares mapping with 3 fields but only 2 are filtered\n");
}
template<class T> void FilterTest::objects() {
setTestCaseTemplateName(Math::TypeTraits<T>::name());
const struct {
T meshMapping[5]{7, 8, 15, 3, 2};
UnsignedByte mesh[5]{2, 222, 3, 222, 222};
T lightMapping[4]{2, 1, 3, 2};
UnsignedInt light[4]{66666, 23, 66666, 66666};
T parentMapping[3]{2, 3, 8};
Short parents[3]{6666, 6666, 6666};
} data[1];
Trade::SceneData scene{Trade::Implementation::sceneMappingTypeFor<T>(), 76, {}, data, {
Trade::SceneFieldData{Trade::SceneField::Mesh,
Containers::arrayView(data->meshMapping),
Containers::arrayView(data->mesh)},
/* This one has duplicate entries for an object, both will be removed */
Trade::SceneFieldData{Trade::SceneField::Light,
Containers::arrayView(data->lightMapping),
Containers::arrayView(data->light)},
/* This one gets all entries removed */
Trade::SceneFieldData{Trade::SceneField::Parent,
Containers::arrayView(data->parentMapping),
Containers::arrayView(data->parents)},
/* This one is already empty */
Trade::SceneFieldData{Trade::SceneField::Camera,
Containers::ArrayView<T>{},
Containers::ArrayView<UnsignedByte>{}},
}};
Containers::BitArray objectsToKeep{DirectInit, std::size_t(scene.mappingBound()), true};
objectsToKeep.reset(8);
objectsToKeep.reset(3);
objectsToKeep.reset(2);
Trade::SceneData filtered = filterObjects(scene, objectsToKeep);
CORRADE_COMPARE(filtered.fieldCount(), 4);
CORRADE_COMPARE(filtered.mappingType(), Trade::Implementation::sceneMappingTypeFor<T>());
CORRADE_COMPARE(filtered.mappingBound(), 76);
CORRADE_VERIFY(filtered.hasField(Trade::SceneField::Mesh));
CORRADE_COMPARE_AS(filtered.mapping<T>(Trade::SceneField::Mesh),
Containers::arrayView<T>({7, 15}),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(filtered.field<UnsignedByte>(Trade::SceneField::Mesh),
Containers::arrayView<UnsignedByte>({2, 3}),
TestSuite::Compare::Container);
CORRADE_VERIFY(filtered.hasField(Trade::SceneField::Light));
CORRADE_COMPARE_AS(filtered.mapping<T>(Trade::SceneField::Light),
Containers::arrayView<T>({1}),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(filtered.field<UnsignedInt>(Trade::SceneField::Light),
Containers::arrayView<UnsignedInt>({23}),
TestSuite::Compare::Container);
/* Parents are all removed */
CORRADE_VERIFY(filtered.hasField(Trade::SceneField::Parent));
CORRADE_COMPARE(filtered.fieldSize(Trade::SceneField::Parent), 0);
/* Cameras were empty before already */
CORRADE_VERIFY(filtered.hasField(Trade::SceneField::Camera));
CORRADE_COMPARE(filtered.fieldSize(Trade::SceneField::Camera), 0);
/* The attribute data should not be a growable array to make this usable in
plugins */
Containers::Array<Trade::SceneFieldData> fieldData = filtered.releaseFieldData();
CORRADE_VERIFY(!fieldData.deleter());
}
void FilterTest::objectsUnchangedFields() {
/* Compared to above, this contains fields that don't have any objects
that should be filtered out, which are thus passed through unchanged
(and thus can be even of type that is unuspported by
filterFieldEntries()) */
const struct {
UnsignedShort meshMapping[5]{7, 8, 15, 3, 2};
UnsignedByte mesh[5]{2, 222, 3, 222, 222};
UnsignedShort visibilityMapping[2]{22, 1};
bool visible[2]{false, true};
} data[1];
Trade::SceneData scene{Trade::SceneMappingType::UnsignedShort, 76, {}, data, {
Trade::SceneFieldData{Trade::SceneField::Mesh,
Containers::arrayView(data->meshMapping),
Containers::arrayView(data->mesh)},
Trade::SceneFieldData{Trade::sceneFieldCustom(15),
Containers::arrayView(data->visibilityMapping),
Containers::stridedArrayView(data->visible).sliceBit(0)},
}};
Containers::BitArray objectsToKeep{DirectInit, std::size_t(scene.mappingBound()), true};
objectsToKeep.reset(8);
objectsToKeep.reset(3);
objectsToKeep.reset(2);
Trade::SceneData filtered = filterObjects(scene, objectsToKeep);
CORRADE_COMPARE(filtered.fieldCount(), 2);
CORRADE_COMPARE(filtered.mappingType(), Trade::SceneMappingType::UnsignedShort);
CORRADE_COMPARE(filtered.mappingBound(), 76);
CORRADE_VERIFY(filtered.hasField(Trade::SceneField::Mesh));
CORRADE_COMPARE_AS(filtered.mapping<UnsignedShort>(Trade::SceneField::Mesh),
Containers::arrayView<UnsignedShort>({7, 15}),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(filtered.field<UnsignedByte>(Trade::SceneField::Mesh),
Containers::arrayView<UnsignedByte>({2, 3}),
TestSuite::Compare::Container);
/* Bits weren't affected and thus were passed through unchanged */
CORRADE_VERIFY(filtered.hasField(Trade::sceneFieldCustom(15)));
CORRADE_COMPARE_AS(filtered.mapping<UnsignedShort>(Trade::sceneFieldCustom(15)),
Containers::arrayView(data->visibilityMapping),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(filtered.fieldBits(Trade::sceneFieldCustom(15)),
Containers::stridedArrayView(data->visible).sliceBit(0),
TestSuite::Compare::Container);
}
void FilterTest::objectsSharedMapping() {
const struct {
UnsignedShort meshMaterialMapping[5]{7, 8, 15, 3, 2};
UnsignedByte mesh[5]{2, 222, 3, 222, 222};
Byte meshMaterial[5]{-1, 111, 7, 111, 111};
UnsignedShort trsMapping[5]{1, 8, 7, 2, 15};
Vector2 translation[5]{
{1.0f, 2.0f},
{},
{3.0f, 4.0f},
{},
{5.0f, 6.0f}
};
Complex rotation[5]{
Complex::rotation(15.0_degf),
{},
Complex::rotation(30.0_degf),
{},
Complex::rotation(45.0_degf)
};
Float uniformScale[5]{10.0f, 0.0f, -5.0f, 0.0f, 555.0f};
UnsignedInt light[2]{34, 25};
Int parent[3]{-1, 0, 3};
} data[1]{};
Trade::SceneData scene{Trade::SceneMappingType::UnsignedShort, 176, {}, data, {
Trade::SceneFieldData{Trade::SceneField::Mesh,
Containers::arrayView(data->meshMaterialMapping),
Containers::arrayView(data->mesh)},
Trade::SceneFieldData{Trade::SceneField::MeshMaterial,
Containers::arrayView(data->meshMaterialMapping),
Containers::arrayView(data->meshMaterial)},
Trade::SceneFieldData{Trade::SceneField::Translation,
Containers::arrayView(data->trsMapping),
Containers::arrayView(data->translation)},
Trade::SceneFieldData{Trade::SceneField::Rotation,
Containers::arrayView(data->trsMapping),
Containers::arrayView(data->rotation)},
/* Shares trsMapping, sharing should be preserved even though not
enforced */
Trade::SceneFieldData{Trade::sceneFieldCustom(15),
Containers::arrayView(data->trsMapping),
Containers::arrayView(data->uniformScale)},
/* Shares a prefix of meshMaterialMapping, should not be preserved */
Trade::SceneFieldData{Trade::SceneField::Light,
Containers::arrayView(data->meshMaterialMapping).prefix(2),
Containers::arrayView(data->light)},
/* Shares every 2nd item of trsMapping, should not be preserved */
Trade::SceneFieldData{Trade::SceneField::Parent,
Containers::stridedArrayView(data->trsMapping).every(2),
Containers::arrayView(data->parent)},
}};
Containers::BitArray objectsToKeep{DirectInit, std::size_t(scene.mappingBound()), true};
objectsToKeep.reset(8);
objectsToKeep.reset(3);
objectsToKeep.reset(2);
Trade::SceneData filtered = filterObjects(scene, objectsToKeep);
CORRADE_COMPARE(filtered.fieldCount(), 7);
CORRADE_COMPARE(filtered.mappingType(), Trade::SceneMappingType::UnsignedShort);
CORRADE_COMPARE(filtered.mappingBound(), 176);
CORRADE_VERIFY(filtered.hasField(Trade::SceneField::Mesh));
CORRADE_COMPARE_AS(filtered.mapping<UnsignedShort>(Trade::SceneField::Mesh),
Containers::arrayView<UnsignedShort>({7, 15}),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(filtered.field<UnsignedByte>(Trade::SceneField::Mesh),
Containers::arrayView<UnsignedByte>({2, 3}),
TestSuite::Compare::Container);
/* Mapping shared with Mesh */
CORRADE_VERIFY(filtered.hasField(Trade::SceneField::MeshMaterial));
CORRADE_COMPARE(filtered.mapping(Trade::SceneField::MeshMaterial).data(),
filtered.mapping(Trade::SceneField::Mesh).data());
CORRADE_COMPARE_AS(filtered.field<Byte>(Trade::SceneField::MeshMaterial),
Containers::arrayView<Byte>({-1, 7}),
TestSuite::Compare::Container);
CORRADE_VERIFY(filtered.hasField(Trade::SceneField::Translation));
CORRADE_COMPARE_AS(filtered.mapping<UnsignedShort>(Trade::SceneField::Translation),
Containers::arrayView<UnsignedShort>({1, 7, 15}),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(filtered.field<Vector2>(Trade::SceneField::Translation),
Containers::arrayView<Vector2>({{1.0f, 2.0f}, {3.0f, 4.0f}, {5.0f, 6.0f}}),
TestSuite::Compare::Container);
/* Mapping shared with Translation */
CORRADE_VERIFY(filtered.hasField(Trade::SceneField::Rotation));
CORRADE_COMPARE(filtered.mapping(Trade::SceneField::Rotation).data(),
filtered.mapping(Trade::SceneField::Translation).data());
CORRADE_COMPARE_AS(filtered.field<Complex>(Trade::SceneField::Rotation),
Containers::arrayView<Complex>({Complex::rotation(15.0_degf), Complex::rotation(30.0_degf), Complex::rotation(45.0_degf)}),
TestSuite::Compare::Container);
/* Mapping shared with Translation again */
CORRADE_VERIFY(filtered.hasField(Trade::sceneFieldCustom(15)));
CORRADE_COMPARE(filtered.mapping(Trade::sceneFieldCustom(15)).data(),
filtered.mapping(Trade::SceneField::Translation).data());
CORRADE_COMPARE_AS(filtered.field<Float>(Trade::sceneFieldCustom(15)),
Containers::arrayView({10.0f, -5.0f, 555.0f}),
TestSuite::Compare::Container);
/* These fields don't share any mapping even though they could */
CORRADE_VERIFY(filtered.hasField(Trade::SceneField::Light));
CORRADE_COMPARE_AS(filtered.mapping<UnsignedShort>(Trade::SceneField::Light),
Containers::arrayView<UnsignedShort>({7}),
TestSuite::Compare::Container);
CORRADE_VERIFY(filtered.mapping(Trade::SceneField::Light).data() != filtered.mapping(Trade::SceneField::Mesh).data());
CORRADE_COMPARE_AS(filtered.field<UnsignedInt>(Trade::SceneField::Light),
Containers::arrayView<UnsignedInt>({34}),
TestSuite::Compare::Container);
CORRADE_VERIFY(filtered.hasField(Trade::SceneField::Parent));
CORRADE_COMPARE_AS(filtered.mapping<UnsignedShort>(Trade::SceneField::Parent),
Containers::arrayView<UnsignedShort>({1, 7, 15}),
TestSuite::Compare::Container);
CORRADE_VERIFY(filtered.mapping(Trade::SceneField::Parent).data() != filtered.mapping(Trade::SceneField::Translation).data());
CORRADE_COMPARE_AS(filtered.field<Int>(Trade::SceneField::Parent),
Containers::arrayView(data->parent),
TestSuite::Compare::Container);
}
void FilterTest::objectsSharedMappingAllRemoved() {
const struct {
UnsignedShort meshMaterialMapping[3]{8, 3, 2};
UnsignedByte mesh[3]{};
UnsignedShort lightMapping[3]{2, 1, 3};
UnsignedInt light[3]{66666, 23, 66666};
Byte meshMaterial[3]{};
} data[1];
Trade::SceneData scene{Trade::SceneMappingType::UnsignedShort, 76, {}, data, {
Trade::SceneFieldData{Trade::SceneField::Mesh,
Containers::arrayView(data->meshMaterialMapping),
Containers::arrayView(data->mesh)},
Trade::SceneFieldData{Trade::SceneField::Light,
Containers::arrayView(data->lightMapping),
Containers::arrayView(data->light)},
Trade::SceneFieldData{Trade::SceneField::MeshMaterial,
Containers::arrayView(data->meshMaterialMapping),
Containers::arrayView(data->meshMaterial)},
}};
Containers::BitArray objectsToKeep{DirectInit, std::size_t(scene.mappingBound()), true};
objectsToKeep.reset(8);
objectsToKeep.reset(3);
objectsToKeep.reset(2);
Trade::SceneData filtered = filterObjects(scene, objectsToKeep);
CORRADE_COMPARE(filtered.fieldCount(), 3);
CORRADE_COMPARE(filtered.mappingType(), Trade::SceneMappingType::UnsignedShort);
CORRADE_COMPARE(filtered.mappingBound(), 76);
CORRADE_VERIFY(filtered.hasField(Trade::SceneField::Mesh));
CORRADE_COMPARE(filtered.fieldSize(Trade::SceneField::Mesh), 0);
/* This one should reuse the (emptied) Mesh mapping instead of going
through everything again */
CORRADE_VERIFY(filtered.hasField(Trade::SceneField::MeshMaterial));
CORRADE_COMPARE(filtered.fieldSize(Trade::SceneField::MeshMaterial), 0);
/* Other fields get filtered as usual */
CORRADE_VERIFY(filtered.hasField(Trade::SceneField::Light));
CORRADE_COMPARE_AS(filtered.mapping<UnsignedShort>(Trade::SceneField::Light),
Containers::arrayView<UnsignedShort>({1}),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(filtered.field<UnsignedInt>(Trade::SceneField::Light),
Containers::arrayView<UnsignedInt>({23}),
TestSuite::Compare::Container);
}
void FilterTest::objectsWrongBitCount() {
CORRADE_SKIP_IF_NO_ASSERT();
Trade::SceneData scene{Trade::SceneMappingType::UnsignedShort, 176, nullptr, {}};
std::ostringstream out;
Error redirectError{&out};
filterObjects(scene, Containers::BitArray{ValueInit, 177});
CORRADE_COMPARE(out.str(), "SceneTools::filterObjects(): expected 176 bits but got 177\n");
}
}}}}
CORRADE_TEST_MAIN(Magnum::SceneTools::Test::FilterTest)

Loading…
Cancel
Save