Browse Source

SceneTools: add filterFieldEntries().

This allows to filter individual field entries in the scene, such as
for example removing certain mesh assignments that were collapsed
together. A higher-level API that allows filtering all data belonging to
a certain set of objects will be then implemented on top of this one.
pull/620/head
Vladimír Vondruš 3 years ago
parent
commit
c32bf7e74c
  1. 19
      doc/snippets/MagnumSceneTools.cpp
  2. 183
      src/Magnum/SceneTools/Filter.cpp
  3. 66
      src/Magnum/SceneTools/Filter.h
  4. 501
      src/Magnum/SceneTools/Test/FilterTest.cpp

19
doc/snippets/MagnumSceneTools.cpp

@ -24,6 +24,8 @@
*/ */
#include <Corrade/Containers/Array.h> #include <Corrade/Containers/Array.h>
#include <Corrade/Containers/BitArray.h>
#include <Corrade/Containers/BitArrayView.h>
#include <Corrade/Containers/GrowableArray.h> #include <Corrade/Containers/GrowableArray.h>
#include <Corrade/Containers/Pair.h> #include <Corrade/Containers/Pair.h>
#include <Corrade/Containers/Triple.h> #include <Corrade/Containers/Triple.h>
@ -32,6 +34,7 @@
#include "Magnum/Math/Matrix4.h" #include "Magnum/Math/Matrix4.h"
#include "Magnum/MeshTools/Concatenate.h" #include "Magnum/MeshTools/Concatenate.h"
#include "Magnum/MeshTools/Transform.h" #include "Magnum/MeshTools/Transform.h"
#include "Magnum/SceneTools/Filter.h"
#include "Magnum/SceneTools/Hierarchy.h" #include "Magnum/SceneTools/Hierarchy.h"
#include "Magnum/Trade/SceneData.h" #include "Magnum/Trade/SceneData.h"
#include "Magnum/Trade/MeshData.h" #include "Magnum/Trade/MeshData.h"
@ -41,6 +44,22 @@
using namespace Magnum; using namespace Magnum;
int main() { int main() {
{
/* [filterFieldEntries-shared-mapping] */
Trade::SceneData scene = DOXYGEN_ELLIPSIS(Trade::SceneData{{}, 0, nullptr, {}});
Containers::BitArray transformationsToKeep = DOXYGEN_ELLIPSIS({});
Containers::BitArray lightsToKeep = DOXYGEN_ELLIPSIS({});
/* Mesh and MeshMaterial fields stay unchanged */
Trade::SceneData filtered = SceneTools::filterFieldEntries(scene, {
{Trade::SceneField::Translation, transformationsToKeep},
{Trade::SceneField::Rotation, transformationsToKeep},
{Trade::SceneField::Light, lightsToKeep}
});
/* [filterFieldEntries-shared-mapping] */
}
{ {
/* [absoluteFieldTransformations2D-mesh-concatenate] */ /* [absoluteFieldTransformations2D-mesh-concatenate] */
Trade::SceneData scene = DOXYGEN_ELLIPSIS(Trade::SceneData{{}, 0, nullptr, {}}); Trade::SceneData scene = DOXYGEN_ELLIPSIS(Trade::SceneData{{}, 0, nullptr, {}});

183
src/Magnum/SceneTools/Filter.cpp

@ -25,10 +25,14 @@
#include "Filter.h" #include "Filter.h"
#include <map>
#include <Corrade/Containers/BitArray.h> #include <Corrade/Containers/BitArray.h>
#include <Corrade/Containers/BitArrayView.h> #include <Corrade/Containers/BitArrayView.h>
#include <Corrade/Containers/Optional.h> #include <Corrade/Containers/Optional.h>
#include <Corrade/Containers/Pair.h>
#include <Corrade/Utility/BitAlgorithms.h>
#include "Magnum/SceneTools/Combine.h"
#include "Magnum/Trade/SceneData.h" #include "Magnum/Trade/SceneData.h"
namespace Magnum { namespace SceneTools { namespace Magnum { namespace SceneTools {
@ -76,4 +80,183 @@ Trade::SceneData filterExceptFields(const Trade::SceneData& scene, std::initiali
return filterExceptFields(scene, Containers::arrayView(fields)); return filterExceptFields(scene, Containers::arrayView(fields));
} }
Trade::SceneData filterFieldEntries(const Trade::SceneData& scene, const Containers::ArrayView<const Containers::Pair<UnsignedInt, Containers::BitArrayView>> entriesToKeep) {
/* Track unique mapping views (pointer, size, stride) so fields that shared
a mapping before stay shared after as well -- if they're filtered, they
will have the mapping allocated in SharedMapping::filteredMapping()
instead of just a null placeholder when passing the filtered fields to
combineFields(), which will ensure they stay shared. If they're not
filtered, the original field view will get passed through, which ensures
the same. This also conveniently handles all cases of enforced mapping
such as for TRS fields so we don't need to special-case that here again. */
struct SharedMapping {
/* How many times given mapping is shared */
UnsignedInt count = 1;
/* How many times given mapping is filtered. Should be either 0 or same
as `count`. */
UnsignedInt filteredCount = 0;
#ifndef CORRADE_NO_ASSERT
/* Index in `entriesToKeep` that contains the filtering mask. All other
entries should use the same view (same pointer, offset and size). */
UnsignedInt maskIndex = ~UnsignedInt{};
#endif
/* Data array allocated for this mapping, in order to have
combineFields() preserve their sharing in the output. Doesn't
contain any actual data, it's used just to have a unique
(pointer, size, stride) combination. */
/** @todo any idea how to do this without the throwaway allocations? */
Containers::Array<char> filteredMapping;
};
/* A map<tuple> is used because it has conveniently implemented ordering,
an unordered_map couldn't be used without manually implementing a
std::tuple hash because STL DOES NOT HAVE IT, UGH. */
std::map<std::tuple<const void*, std::size_t, std::ptrdiff_t>, SharedMapping> 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);
const std::pair<std::map<std::tuple<const void*, std::size_t, std::ptrdiff_t>, SharedMapping>::iterator, bool> inserted = uniqueMappings.emplace(std::make_tuple(mapping.data(), mapping.size()[0], mapping.stride()[0]), SharedMapping{});
if(!inserted.second)
++inserted.first->second.count;
}
/* Copy all field metadata. By default, if the field isn't referenced, it's
kept in full. Can't use Utility::copy() on the whole fieldData() array
as those can be offset-only. */
Containers::Array<Trade::SceneFieldData> fields{ValueInit, scene.fieldCount()};
for(std::size_t i = 0; i != scene.fieldCount(); ++i)
fields[i] = scene.fieldData(i);
const std::size_t mappingTypeSize = Trade::sceneMappingTypeSize(scene.mappingType());
/* For fields that are being filtered update the field size and turn it
into a placeholder */
#ifndef CORRADE_NO_ASSERT
Containers::BitArray usedFields{ValueInit, scene.fieldCount()};
#endif
for(std::size_t i = 0; i != entriesToKeep.size(); ++i) {
const UnsignedInt fieldId = entriesToKeep[i].first();
const Containers::BitArrayView mask = entriesToKeep[i].second();
CORRADE_ASSERT(fieldId < scene.fieldCount(),
"SceneTools::filterFieldEntries(): index" << fieldId << "out of range for" << scene.fieldCount() << "fields", (Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}}));
CORRADE_ASSERT(!usedFields[fieldId],
"SceneTools::filterFieldEntries(): field" << scene.fieldName(fieldId) << "listed more than once", (Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}}));
#ifndef CORRADE_NO_ASSERT
usedFields.set(fieldId);
#endif
CORRADE_ASSERT(scene.fieldSize(fieldId) == mask.size(),
"SceneTools::filterFieldEntries(): expected" << scene.fieldSize(fieldId) << "bits for" << scene.fieldName(fieldId) << "but got" << mask.size(),
(Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}}));
const Trade::SceneFieldType fieldType = scene.fieldType(fieldId);
CORRADE_ASSERT(!Trade::Implementation::isSceneFieldTypeString(fieldType),
"SceneTools::filterFieldEntries(): filtering string fields is not implemented yet, sorry", (Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}}));
CORRADE_ASSERT(fieldType != Trade::SceneFieldType::Bit,
"SceneTools::filterFieldEntries(): filtering bit fields is not implemented yet, sorry", (Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}}));
/* Skip empty fields as there's nothing to do for them and they don't
even have an entry in the uniqueMappings map. But do that only after
doing all checks for them for consistent behavior. */
if(!scene.fieldSize(fieldId))
continue;
const Containers::StridedArrayView2D<const char> mapping = scene.mapping(fieldId);
SharedMapping& sharedMapping = uniqueMappings.at(std::make_tuple(mapping.data(), mapping.size()[0], mapping.stride()[0]));
/* If the mapping is shared, pass a pre-allocated array with the final
contents to combineFields() to keep the sharing */
const std::size_t filteredFieldSize = mask.count();
Containers::StridedArrayView1D<const void> filteredMapping;
if(sharedMapping.count > 1) {
/* This is the first mask that filters a shared mapping, allocate
the output for it and copy the filtered mapping there */
if(!sharedMapping.filteredCount) {
sharedMapping.filteredMapping = Containers::Array<char>{NoInit, mappingTypeSize*filteredFieldSize};
Utility::copyMasked(scene.mapping(fieldId), mask, Containers::StridedArrayView2D<char>{sharedMapping.filteredMapping, {filteredFieldSize, mappingTypeSize}});
#ifndef CORRADE_NO_ASSERT
sharedMapping.maskIndex = i;
#endif
}
#ifndef CORRADE_NO_ASSERT
/* Otherwise check that all shared fields use the same filter
view */
else {
#ifndef CORRADE_STANDARD_ASSERT
const UnsignedInt originalFieldId = entriesToKeep[sharedMapping.maskIndex].first();
#endif
const Containers::BitArrayView originalMask = entriesToKeep[sharedMapping.maskIndex].second();
CORRADE_ASSERT(
originalMask.data() == mask.data() &&
originalMask.offset() == mask.offset() &&
originalMask.size() == mask.size(),
"SceneTools::filterFieldEntries(): field" << scene.fieldName(fieldId) << "shares mapping with" << scene.fieldName(originalFieldId) << "but was passed a different mask view",
(Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}}));
}
#endif
filteredMapping = {sharedMapping.filteredMapping, filteredFieldSize, std::ptrdiff_t(mappingTypeSize)};
} else {
CORRADE_INTERNAL_ASSERT(sharedMapping.count == 1);
filteredMapping = {{nullptr, mappingTypeSize*filteredFieldSize}, filteredFieldSize, std::ptrdiff_t(mappingTypeSize)};
}
const std::size_t fieldTypeSize = Trade::sceneFieldTypeSize(fieldType);
fields[fieldId] = Trade::SceneFieldData{scene.fieldName(fieldId),
scene.mappingType(), filteredMapping,
fieldType, Containers::StridedArrayView1D<const void>{{nullptr, fieldTypeSize*filteredFieldSize}, filteredFieldSize, std::ptrdiff_t(fieldTypeSize)}, scene.fieldArraySize(fieldId)};
++sharedMapping.filteredCount;
}
#ifndef CORRADE_NO_ASSERT
for(const std::pair<const std::tuple<const void*, std::size_t, std::ptrdiff_t>, SharedMapping>& i: uniqueMappings) {
CORRADE_ASSERT(!i.second.filteredCount || i.second.count == i.second.filteredCount,
"SceneTools::filterFieldEntries(): field" << scene.fieldName(entriesToKeep[i.second.maskIndex].first()) << "shares mapping with" << i.second.count << "fields but only" << i.second.filteredCount << "are filtered",
(Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}}));
}
#endif
Trade::SceneData out = combineFields(scene.mappingType(), scene.mappingBound(), fields);
for(const Containers::Pair<UnsignedInt, Containers::BitArrayView>& i: entriesToKeep) {
/* 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(i.first()))
continue;
/* Copy the mapping only if it isn't shared among more fields -- in
that case it got already copied above */
const Containers::StridedArrayView2D<const char> mapping = scene.mapping(i.first());
if(uniqueMappings.at(std::make_tuple(mapping.data(), mapping.size()[0], mapping.stride()[0])).count == 1)
Utility::copyMasked(mapping, i.second(), out.mutableMapping(i.first()));
Utility::copyMasked(scene.field(i.first()), i.second(), out.mutableField(i.first()));
}
return out;
}
Trade::SceneData filterFieldEntries(const Trade::SceneData& scene, const std::initializer_list<Containers::Pair<UnsignedInt, Containers::BitArrayView>> entriesToKeep) {
return filterFieldEntries(scene, Containers::arrayView(entriesToKeep));
}
Trade::SceneData filterFieldEntries(const Trade::SceneData& scene, const Containers::ArrayView<const Containers::Pair<Trade::SceneField, Containers::BitArrayView>> entriesToKeep) {
Containers::Array<Containers::Pair<UnsignedInt, Containers::BitArrayView>> out{NoInit, entriesToKeep.size()};
for(std::size_t i = 0; i != entriesToKeep.size(); ++i) {
const Containers::Optional<UnsignedInt> fieldId = scene.findFieldId(entriesToKeep[i].first());
CORRADE_ASSERT(fieldId,
"SceneTools::filterFieldEntries(): field" << entriesToKeep[i].first() << "not found", (Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}}));
out[i] = {*fieldId, entriesToKeep[i].second()};
}
return filterFieldEntries(scene, out);
}
Trade::SceneData filterFieldEntries(const Trade::SceneData& scene, const std::initializer_list<Containers::Pair<Trade::SceneField, Containers::BitArrayView>> entriesToKeep) {
return filterFieldEntries(scene, Containers::arrayView(entriesToKeep));
}
}} }}

66
src/Magnum/SceneTools/Filter.h

@ -26,7 +26,7 @@
*/ */
/** @file /** @file
* @brief Function @ref Magnum::SceneTools::filterFields(), @ref Magnum::SceneTools::filterOnlyFields(), @ref Magnum::SceneTools::filterExceptFields() * @brief Function @ref Magnum::SceneTools::filterFields(), @ref Magnum::SceneTools::filterOnlyFields(), @ref Magnum::SceneTools::filterExceptFields(), @ref Magnum::SceneTools::filterFieldEntries()
* @m_since_latest * @m_since_latest
*/ */
@ -93,6 +93,70 @@ MAGNUM_SCENETOOLS_EXPORT Trade::SceneData filterExceptFields(const Trade::SceneD
*/ */
MAGNUM_SCENETOOLS_EXPORT Trade::SceneData filterExceptFields(const Trade::SceneData& scene, std::initializer_list<Trade::SceneField> fields); MAGNUM_SCENETOOLS_EXPORT Trade::SceneData filterExceptFields(const Trade::SceneData& scene, std::initializer_list<Trade::SceneField> fields);
/**
@brief Filter individual entries of fields in a scene
@m_since_latest
Returns a copy of @p scene containing the same fields but only with entries for
which the corresponding bit in @p entriesToKeep is set. Each item in
@p entriesToKeep is a pair of a field ID and a mask of entries to keep in that
field. The field ID is expected to be unique in the list and less than
@ref Trade::SceneData::fieldCount(), size of the mask then equal to
@ref Trade::SceneData::fieldSize() for that field. Fields not listed in the
@p entriesToKeep array are passed through unchanged, use @ref filterFields(),
@ref filterExceptFields() or @ref filterOnlyFields() to deal with them as a
whole if needed.
Fields that fully share their mapping views (such as @ref Trade::SceneField::Mesh
and @relativeref{Trade::SceneField,MeshMaterial}, including fields for which
this isn't enforced) either need to be listed all in @p entriesToKeep with the
same mask view, or all omitted so they're passed through. Fields that share the
mapping only partially don't have any special handling. The data repacking is
performed using @ref combineFields(), see its documentation for more
information.
As an example, let's assume in the following snippet the scene contains
@ref Trade::SceneField::Translation,
@relativeref{Trade::SceneField,Rotation}, @relativeref{Trade::SceneField,Mesh},
@relativeref{Trade::SceneField,MeshMaterial} and
@relativeref{Trade::SceneField,Light}, with the intent to filter some
translations and lights away. Filtering translations means the rotations have
to be filtered as well, however neither meshes nor materials (which share the
mapping as well) need to be listed if they're not filtered:
@snippet MagnumSceneTools.cpp filterFieldEntries-shared-mapping
At the moment, @ref Trade::SceneFieldType::Bit and string fields can't be
filtered, only passed through.
@experimental
*/
MAGNUM_SCENETOOLS_EXPORT Trade::SceneData filterFieldEntries(const Trade::SceneData& scene, Containers::ArrayView<const Containers::Pair<UnsignedInt, Containers::BitArrayView>> entriesToKeep);
/**
@overload
@m_since_latest
*/
MAGNUM_SCENETOOLS_EXPORT Trade::SceneData filterFieldEntries(const Trade::SceneData& scene, std::initializer_list<Containers::Pair<UnsignedInt, Containers::BitArrayView>> entriesToKeep);
/**
@brief Filter individual entries of named fields in a scene
@m_since_latest
Translates field names in @p entriesToKeep to field IDs using
@ref Trade::SceneData::fieldId() and delegates to
@ref filterFieldEntries(const Trade::SceneData&, Containers::ArrayView<const Containers::Pair<UnsignedInt, Containers::BitArrayView>>).
Expects that all listed fields exist in @p scene, see the referenced function
documentation for other expectations.
@experimental
*/
MAGNUM_SCENETOOLS_EXPORT Trade::SceneData filterFieldEntries(const Trade::SceneData& scene, Containers::ArrayView<const Containers::Pair<Trade::SceneField, Containers::BitArrayView>> entriesToKeep);
/**
@overload
@m_since_latest
*/
MAGNUM_SCENETOOLS_EXPORT Trade::SceneData filterFieldEntries(const Trade::SceneData& scene, std::initializer_list<Containers::Pair<Trade::SceneField, Containers::BitArrayView>> entriesToKeep);
}} }}
#endif #endif

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

@ -25,10 +25,14 @@
#include <sstream> #include <sstream>
#include <Corrade/Containers/BitArray.h> #include <Corrade/Containers/BitArray.h>
#include <Corrade/Containers/Pair.h>
#include <Corrade/Containers/StridedBitArrayView.h> #include <Corrade/Containers/StridedBitArrayView.h>
#include <Corrade/TestSuite/Tester.h> #include <Corrade/TestSuite/Tester.h>
#include <Corrade/TestSuite/Compare/Container.h>
#include <Corrade/Utility/DebugStl.h> #include <Corrade/Utility/DebugStl.h>
#include "Magnum/Math/Complex.h"
#include "Magnum/Math/Vector2.h"
#include "Magnum/SceneTools/Filter.h" #include "Magnum/SceneTools/Filter.h"
#include "Magnum/Trade/SceneData.h" #include "Magnum/Trade/SceneData.h"
@ -45,6 +49,26 @@ struct FilterTest: TestSuite::Tester {
void exceptFields(); void exceptFields();
void exceptFieldsNoFieldData(); void exceptFieldsNoFieldData();
void fieldEntries();
void fieldEntriesFieldNotFound();
void fieldEntriesDuplicated();
void fieldEntriesWrongBitCount();
void fieldEntriesStringField();
void fieldEntriesBitField();
void fieldEntriesSharedMapping();
void fieldEntriesSharedMappingInvalid();
};
using namespace Math::Literals;
const struct {
const char* name;
bool byName;
} FieldEntriesData[]{
{"by ID", false},
{"by name", true}
}; };
FilterTest::FilterTest() { FilterTest::FilterTest() {
@ -56,6 +80,18 @@ FilterTest::FilterTest() {
&FilterTest::exceptFields, &FilterTest::exceptFields,
&FilterTest::exceptFieldsNoFieldData}); &FilterTest::exceptFieldsNoFieldData});
addInstancedTests({&FilterTest::fieldEntries},
Containers::arraySize(FieldEntriesData));
addTests({&FilterTest::fieldEntriesFieldNotFound,
&FilterTest::fieldEntriesDuplicated,
&FilterTest::fieldEntriesWrongBitCount,
&FilterTest::fieldEntriesStringField,
&FilterTest::fieldEntriesBitField,
&FilterTest::fieldEntriesSharedMapping,
&FilterTest::fieldEntriesSharedMappingInvalid});
} }
void FilterTest::fields() { void FilterTest::fields() {
@ -259,6 +295,471 @@ void FilterTest::exceptFieldsNoFieldData() {
CORRADE_COMPARE(filtered.dataFlags(), Trade::DataFlags{}); CORRADE_COMPARE(filtered.dataFlags(), Trade::DataFlags{});
} }
void FilterTest::fieldEntries() {
auto&& data = FieldEntriesData[testCaseInstanceId()];
setTestCaseDescription(data.name);
const struct Data {
UnsignedShort meshMapping[5]{7, 8, 6666, 3, 6666};
UnsignedByte mesh[5]{2, 3, 222, 1, 222};
UnsignedShort lightMapping[4]{3, 1, 2, 2};
UnsignedInt light[4]{12, 23, 32, 31};
UnsignedShort arrayMapping[3]{6666, 3, 2};
Float array[3][2]{{77.0f, 88.0f}, {1.0f, 2.0f}, {3.0f, 4.0f}};
UnsignedShort visibilityMapping[2]{12, 33};
bool visible[2]{true, false};
UnsignedShort parentMapping[3]{};
Short parents[3]{};
} sceneData[1]{};
Trade::SceneData scene{Trade::SceneMappingType::UnsignedShort, 76, {}, sceneData, {
Trade::SceneFieldData{Trade::SceneField::Mesh,
Containers::arrayView(sceneData->meshMapping),
Containers::arrayView(sceneData->mesh)},
/* Offset-only, to verify it get converted to absolute when it reaches
combine() at the end */
Trade::SceneFieldData{Trade::SceneField::Light, 4,
Trade::SceneMappingType::UnsignedShort, offsetof(Data, lightMapping), sizeof(UnsignedShort),
Trade::SceneFieldType::UnsignedInt, offsetof(Data, light), sizeof(UnsignedInt)},
/* Array */
Trade::SceneFieldData{Trade::sceneFieldCustom(333),
Containers::arrayView(sceneData->arrayMapping),
Containers::arrayCast<2, const Float>(Containers::stridedArrayView(sceneData->array))},
/* Bit field. Should cause no assert as it's just passed through. */
Trade::SceneFieldData{Trade::sceneFieldCustom(15),
Containers::arrayView(sceneData->visibilityMapping),
Containers::stridedArrayView(sceneData->visible).sliceBit(0)},
/* This one gets all entries removed */
Trade::SceneFieldData{Trade::SceneField::Parent,
Containers::arrayView(sceneData->parentMapping),
Containers::arrayView(sceneData->parents)},
/* This one is already empty */
Trade::SceneFieldData{Trade::SceneField::Camera,
Containers::ArrayView<UnsignedShort>{},
Containers::ArrayView<UnsignedByte>{}},
}};
Containers::BitArray meshesToKeep{DirectInit, Containers::arraySize(sceneData->mesh), true};
meshesToKeep.reset(2);
meshesToKeep.reset(4);
Containers::BitArray arraysToKeep{DirectInit, Containers::arraySize(sceneData->array), true};
arraysToKeep.reset(0);
Containers::BitArray parentsToKeep{DirectInit, Containers::arraySize(sceneData->parents), false};
Containers::BitArray camerasToKeep;
Trade::SceneData filtered = data.byName ?
filterFieldEntries(scene, {
{Trade::sceneFieldCustom(333), arraysToKeep},
{Trade::SceneField::Parent, parentsToKeep},
{Trade::SceneField::Mesh, meshesToKeep},
{Trade::SceneField::Camera, camerasToKeep}
}) :
filterFieldEntries(scene, {
{2, arraysToKeep},
{4, parentsToKeep},
{0, meshesToKeep},
{5, camerasToKeep}
});
CORRADE_COMPARE(filtered.fieldCount(), 6);
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, 8, 3}),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(filtered.field<UnsignedByte>(Trade::SceneField::Mesh),
Containers::arrayView<UnsignedByte>({2, 3, 1}),
TestSuite::Compare::Container);
/* Lights weren't listed and thus stayed untouched */
CORRADE_VERIFY(filtered.hasField(Trade::SceneField::Light));
CORRADE_COMPARE_AS(filtered.mapping<UnsignedShort>(Trade::SceneField::Light),
Containers::arrayView(sceneData->lightMapping),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(filtered.field<UnsignedInt>(Trade::SceneField::Light),
Containers::arrayView(sceneData->light),
TestSuite::Compare::Container);
CORRADE_VERIFY(filtered.hasField(Trade::sceneFieldCustom(333)));
CORRADE_COMPARE_AS(filtered.mapping<UnsignedShort>(Trade::sceneFieldCustom(333)),
Containers::arrayView<UnsignedShort>({3, 2}),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS((Containers::arrayCast<1, const Vector2>(filtered.field<Float[]>(Trade::sceneFieldCustom(333)))),
Containers::arrayView<Vector2>({{1.0f, 2.0f}, {3.0f, 4.0f}}),
TestSuite::Compare::Container);
/* Bits weren't listed and thus stayed untouched */
CORRADE_VERIFY(filtered.hasField(Trade::sceneFieldCustom(15)));
CORRADE_COMPARE_AS(filtered.mapping<UnsignedShort>(Trade::sceneFieldCustom(15)),
Containers::arrayView(sceneData->visibilityMapping),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(filtered.fieldBits(Trade::sceneFieldCustom(15)),
Containers::stridedArrayView(sceneData->visible).sliceBit(0),
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::fieldEntriesFieldNotFound() {
CORRADE_SKIP_IF_NO_ASSERT();
const struct {
UnsignedShort meshMapping[5];
UnsignedByte mesh[5];
UnsignedShort lightMapping[4];
UnsignedInt light[4];
} 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::SceneField::Light,
Containers::arrayView(data->lightMapping),
Containers::arrayView(data->light)},
}};
std::ostringstream out;
Error redirectError{&out};
filterFieldEntries(scene, {
{Trade::SceneField::Light, Containers::BitArray{ValueInit, 4}},
{Trade::SceneField::Parent, {}}
});
filterFieldEntries(scene, {
{1, Containers::BitArray{ValueInit, 4}},
{2, {}}
});
CORRADE_COMPARE(out.str(),
"SceneTools::filterFieldEntries(): field Trade::SceneField::Parent not found\n"
"SceneTools::filterFieldEntries(): index 2 out of range for 2 fields\n");
}
void FilterTest::fieldEntriesDuplicated() {
CORRADE_SKIP_IF_NO_ASSERT();
const struct {
UnsignedShort meshMapping[5];
UnsignedByte mesh[5];
UnsignedShort lightMapping[4];
UnsignedInt light[4];
} 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::SceneField::Light,
Containers::arrayView(data->lightMapping),
Containers::arrayView(data->light)},
}};
std::ostringstream out;
Error redirectError{&out};
/* The name-based variant just delegates to this one, no need to test it
as well */
filterFieldEntries(scene, {
{1, Containers::BitArray{ValueInit, 4}},
{0, Containers::BitArray{ValueInit, 5}},
{1, Containers::BitArray{ValueInit, 4}},
});
CORRADE_COMPARE(out.str(), "SceneTools::filterFieldEntries(): field Trade::SceneField::Light listed more than once\n");
}
void FilterTest::fieldEntriesWrongBitCount() {
CORRADE_SKIP_IF_NO_ASSERT();
const struct {
UnsignedShort meshMapping[5];
UnsignedByte mesh[5];
UnsignedShort lightMapping[4];
UnsignedInt light[4];
} 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::SceneField::Light,
Containers::arrayView(data->lightMapping),
Containers::arrayView(data->light)},
}};
std::ostringstream out;
Error redirectError{&out};
/* The name-based variant just delegates to this one, no need to test it
as well */
filterFieldEntries(scene, {
{1, Containers::BitArray{ValueInit, 4}},
{0, Containers::BitArray{ValueInit, 6}}
});
CORRADE_COMPARE(out.str(), "SceneTools::filterFieldEntries(): expected 5 bits for Trade::SceneField::Mesh but got 6\n");
}
void FilterTest::fieldEntriesBitField() {
CORRADE_SKIP_IF_NO_ASSERT();
const struct {
UnsignedShort meshMapping[5];
UnsignedByte mesh[5];
UnsignedShort visibilityMapping[2];
bool visible[2];
} 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)},
}};
std::ostringstream out;
Error redirectError{&out};
/* The name-based variant just delegates to this one, no need to test it
as well */
filterFieldEntries(scene, {
{0, Containers::BitArray{ValueInit, 5}},
{1, Containers::BitArray{ValueInit, 2}}
});
CORRADE_COMPARE(out.str(), "SceneTools::filterFieldEntries(): filtering bit fields is not implemented yet, sorry\n");
}
void FilterTest::fieldEntriesStringField() {
CORRADE_SKIP_IF_NO_ASSERT();
const struct {
UnsignedShort meshMapping[5];
UnsignedByte mesh[5];
UnsignedShort nameMapping[2];
UnsignedInt nameRangeNullTerminated[2];
char nameString[1];
} 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->nameMapping),
data->nameString, Trade::SceneFieldType::StringRangeNullTerminated32,
Containers::arrayView(data->nameRangeNullTerminated)},
}};
std::ostringstream out;
Error redirectError{&out};
/* The name-based variant just delegates to this one, no need to test it
as well */
filterFieldEntries(scene, {
{0, Containers::BitArray{ValueInit, 5}},
{1, Containers::BitArray{ValueInit, 2}}
});
CORRADE_COMPARE(out.str(), "SceneTools::filterFieldEntries(): filtering string fields is not implemented yet, sorry\n");
}
void FilterTest::fieldEntriesSharedMapping() {
const struct {
UnsignedShort meshMaterialMapping[5]{7, 8, 6666, 6666, 3};
UnsignedByte mesh[5]{2, 3, 222, 222, 1};
Byte meshMaterial[5]{-1, 7, 111, 111, 0};
UnsignedShort trsMapping[5]{1, 6666, 7, 6666, 3};
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 meshesToKeep{DirectInit, Containers::arraySize(data->mesh), true};
meshesToKeep.reset(2);
meshesToKeep.reset(3);
Containers::BitArray transformationsToKeep{DirectInit, Containers::arraySize(data->trsMapping), true};
transformationsToKeep.reset(1);
transformationsToKeep.reset(3);
Trade::SceneData filtered = filterFieldEntries(scene, {
/* All shared fields have to be listed with the same view */
{Trade::SceneField::Mesh, meshesToKeep},
{Trade::SceneField::MeshMaterial, meshesToKeep},
{Trade::SceneField::Translation, transformationsToKeep},
{Trade::SceneField::Rotation, transformationsToKeep},
{Trade::sceneFieldCustom(15), transformationsToKeep},
});
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, 8, 3}),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(filtered.field<UnsignedByte>(Trade::SceneField::Mesh),
Containers::arrayView<UnsignedByte>({2, 3, 1}),
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, 0}),
TestSuite::Compare::Container);
CORRADE_VERIFY(filtered.hasField(Trade::SceneField::Translation));
CORRADE_COMPARE_AS(filtered.mapping<UnsignedShort>(Trade::SceneField::Translation),
Containers::arrayView<UnsignedShort>({1, 7, 3}),
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 are kept unfiltered and they 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, 8}),
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(data->light),
TestSuite::Compare::Container);
CORRADE_VERIFY(filtered.hasField(Trade::SceneField::Parent));
CORRADE_COMPARE_AS(filtered.mapping<UnsignedShort>(Trade::SceneField::Parent),
Containers::arrayView<UnsignedShort>({1, 7, 3}),
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::fieldEntriesSharedMappingInvalid() {
CORRADE_SKIP_IF_NO_ASSERT();
const struct {
UnsignedShort lightMapping[4];
UnsignedInt light[4];
UnsignedShort meshMaterialMapping[5];
UnsignedByte mesh[5];
Byte meshMaterial[5];
UnsignedLong meshIndexOffset[5];
} data[1]{};
Trade::SceneData scene{Trade::SceneMappingType::UnsignedShort, 176, {}, data, {
Trade::SceneFieldData{Trade::SceneField::Light,
Containers::arrayView(data->lightMapping),
Containers::arrayView(data->light)},
Trade::SceneFieldData{Trade::SceneField::Mesh,
Containers::arrayView(data->meshMaterialMapping),
Containers::arrayView(data->mesh)},
Trade::SceneFieldData{Trade::sceneFieldCustom(1),
Containers::arrayView(data->meshMaterialMapping),
Containers::arrayView(data->meshIndexOffset)},
Trade::SceneFieldData{Trade::SceneField::MeshMaterial,
Containers::arrayView(data->meshMaterialMapping),
Containers::arrayView(data->meshMaterial)},
}};
Containers::BitArray meshesToKeep{ValueInit, 5};
Containers::BitArray meshesToKeepDifferent{DirectInit, 5, true};
std::ostringstream out;
Error redirectError{&out};
filterFieldEntries(scene, {
{Trade::SceneField::MeshMaterial, meshesToKeep},
{Trade::SceneField::Mesh, meshesToKeep},
{Trade::sceneFieldCustom(1), meshesToKeepDifferent},
});
filterFieldEntries(scene, {
{Trade::SceneField::Mesh, meshesToKeep},
{Trade::SceneField::MeshMaterial, meshesToKeep},
});
filterFieldEntries(scene, {
{Trade::sceneFieldCustom(1), meshesToKeep},
{Trade::SceneField::MeshMaterial, meshesToKeep},
});
CORRADE_COMPARE(out.str(),
"SceneTools::filterFieldEntries(): field Trade::SceneField::Custom(1) shares mapping with Trade::SceneField::MeshMaterial but was passed a different mask view\n"
"SceneTools::filterFieldEntries(): field Trade::SceneField::Mesh shares mapping with 3 fields but only 2 are filtered\n"
"SceneTools::filterFieldEntries(): field Trade::SceneField::Custom(1) shares mapping with 3 fields but only 2 are filtered\n");
}
}}}} }}}}
CORRADE_TEST_MAIN(Magnum::SceneTools::Test::FilterTest) CORRADE_TEST_MAIN(Magnum::SceneTools::Test::FilterTest)

Loading…
Cancel
Save