Browse Source

SceneTools: new set of tools for remapping index fields.

To be used with the recently added MaterialTools::removeDuplicates() for
example, or internally by other upcoming scene tools such as importer
filtering.
pull/168/head
Vladimír Vondruš 2 years ago
parent
commit
e0c3bda762
  1. 9
      doc/snippets/CMakeLists.txt
  2. 66
      doc/snippets/MagnumMaterialTools.cpp
  3. 10
      src/Magnum/MaterialTools/RemoveDuplicates.h
  4. 4
      src/Magnum/SceneTools/CMakeLists.txt
  5. 201
      src/Magnum/SceneTools/Map.cpp
  6. 145
      src/Magnum/SceneTools/Map.h
  7. 1
      src/Magnum/SceneTools/Test/CMakeLists.txt
  8. 761
      src/Magnum/SceneTools/Test/MapTest.cpp

9
doc/snippets/CMakeLists.txt

@ -99,6 +99,15 @@ if(MAGNUM_WITH_GL)
endif() endif()
endif() endif()
if(MAGNUM_WITH_MATERIALTOOLS)
add_library(snippets-MagnumMaterialTools STATIC ${EXCLUDE_FROM_ALL_IF_TEST_TARGET}
MagnumMaterialTools.cpp)
target_link_libraries(snippets-MagnumMaterialTools PRIVATE MagnumMaterialTools)
if(CORRADE_TESTSUITE_TEST_TARGET)
add_dependencies(${CORRADE_TESTSUITE_TEST_TARGET} snippets-MagnumMaterialTools)
endif()
endif()
if(MAGNUM_WITH_MESHTOOLS) if(MAGNUM_WITH_MESHTOOLS)
add_library(snippets-MagnumMeshTools STATIC ${EXCLUDE_FROM_ALL_IF_TEST_TARGET} add_library(snippets-MagnumMeshTools STATIC ${EXCLUDE_FROM_ALL_IF_TEST_TARGET}
MagnumMeshTools.cpp) MagnumMeshTools.cpp)

66
doc/snippets/MagnumMaterialTools.cpp

@ -0,0 +1,66 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021, 2022, 2023 Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include <Corrade/Containers/GrowableArray.h>
#include <Corrade/Containers/Iterable.h>
#include <Corrade/PluginManager/Manager.h>
#include "Magnum/MaterialTools/RemoveDuplicates.h"
#include "Magnum/SceneTools/Map.h"
#include "Magnum/Trade/AbstractImporter.h"
#include "Magnum/Trade/MaterialData.h"
#include "Magnum/Trade/SceneData.h"
#define DOXYGEN_ELLIPSIS(...) __VA_ARGS__
using namespace Magnum;
int main() {
{
/* -Wnonnull in GCC 11+ "helpfully" says "this is null" if I don't initialize
the font pointer. I don't care, I just want you to check compilation errors,
not more! */
PluginManager::Manager<Trade::AbstractImporter> manager;
/* [removeDuplicatesInPlace] */
Containers::Pointer<Trade::AbstractImporter> importer = DOXYGEN_ELLIPSIS(manager.loadAndInstantiate("SomethingWhatever"));
/* Import all materials */
Containers::Array<Trade::MaterialData> materials;
for(UnsignedInt i = 0; i != importer->materialCount(); ++i)
arrayAppend(materials, *importer->material(i));
/* Remove duplicates, putting the unique materials to the prefix and removing
the rest */
Containers::Pair<Containers::Array<UnsignedInt>, std::size_t> mapping =
MaterialTools::removeDuplicatesInPlace(materials);
arrayRemoveSuffix(materials, materials.size() - mapping.second());
/* Apply the mapping to unique materials to the scene */
Trade::SceneData scene = DOXYGEN_ELLIPSIS(Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}});
SceneTools::mapIndexFieldInPlace(scene,
Trade::SceneField::MeshMaterial, mapping.first());
/* [removeDuplicatesInPlace] */
}
}

10
src/Magnum/MaterialTools/RemoveDuplicates.h

@ -61,6 +61,13 @@ count --- every material in the list is compared to all unique materials
collected so far. As attributes are sorted in @ref Trade::MaterialData, collected so far. As attributes are sorted in @ref Trade::MaterialData,
material comparison is just a linear operation. The function doesn't allocate material comparison is just a linear operation. The function doesn't allocate
any temporary memory. any temporary memory.
The output index array can be passed to @ref SceneTools::mapIndexField() to
update a @ref Trade::SceneField::MeshMaterial field to reference only the
unique materials. For example:
@snippet MagnumMaterialTools.cpp removeDuplicatesInPlace
@see @ref removeDuplicatesInPlaceInto() @see @ref removeDuplicatesInPlaceInto()
*/ */
MAGNUM_MATERIALTOOLS_EXPORT Containers::Pair<Containers::Array<UnsignedInt>, std::size_t> removeDuplicatesInPlace(const Containers::Iterable<Trade::MaterialData>& materials); MAGNUM_MATERIALTOOLS_EXPORT Containers::Pair<Containers::Array<UnsignedInt>, std::size_t> removeDuplicatesInPlace(const Containers::Iterable<Trade::MaterialData>& materials);
@ -92,7 +99,8 @@ values are compared using fuzzy comparison. Importer state and data flags
aren't considered when comparing the materials. The returned mapping array has aren't considered when comparing the materials. The returned mapping array has
the same size as the @p materials list and maps from the original indices to the same size as the @p materials list and maps from the original indices to
only unique materials in the input array. See @ref removeDuplicatesInPlace() only unique materials in the input array. See @ref removeDuplicatesInPlace()
for a variant that also shifts the unique materials to the front of the list. for a variant that also shifts the unique materials to the front of the list
and for a practical usage example.
The operation is done in an @f$ \mathcal{O}(n^2 m) @f$ complexity with The operation is done in an @f$ \mathcal{O}(n^2 m) @f$ complexity with
@f$ n @f$ being the material list size and @f$ m @f$ the per-material attribute @f$ n @f$ being the material list size and @f$ m @f$ the per-material attribute

4
src/Magnum/SceneTools/CMakeLists.txt

@ -46,12 +46,14 @@ set(MagnumSceneTools_SRCS
set(MagnumSceneTools_GracefulAssert_SRCS set(MagnumSceneTools_GracefulAssert_SRCS
Combine.cpp Combine.cpp
Filter.cpp Filter.cpp
Hierarchy.cpp) Hierarchy.cpp
Map.cpp)
set(MagnumSceneTools_HEADERS set(MagnumSceneTools_HEADERS
Combine.h Combine.h
Filter.h Filter.h
Hierarchy.h Hierarchy.h
Map.h
visibility.h) visibility.h)

201
src/Magnum/SceneTools/Map.cpp

@ -0,0 +1,201 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021, 2022, 2023 Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include "Map.h"
#include <Corrade/Containers/Optional.h>
#include "Magnum/Math/PackingBatch.h"
#include "Magnum/SceneTools/Combine.h"
#include "Magnum/Trade/SceneData.h"
namespace Magnum { namespace SceneTools {
Trade::SceneData mapIndexField(const Trade::SceneData& scene, const UnsignedInt fieldId, const Containers::StridedArrayView1D<const UnsignedInt>& mapping) {
CORRADE_ASSERT(fieldId < scene.fieldCount(),
"SceneTools::mapIndexField(): index" << fieldId << "out of range for" << scene.fieldCount() << "fields",
(Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}}));
const Trade::SceneFieldType fieldType = scene.fieldType(fieldId);
Containers::Array<Trade::SceneFieldData> fields{NoInit, scene.fieldCount()};
for(std::size_t i = 0; i != scene.fieldCount(); ++i) {
if(i == fieldId) {
CORRADE_ASSERT(scene.fieldArraySize(i) == 0,
"SceneTools::mapIndexField(): array field mapping isn't supported",
(Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}}));
Trade::SceneFieldType outputFieldType;
if(fieldType == Trade::SceneFieldType::UnsignedInt ||
fieldType == Trade::SceneFieldType::UnsignedShort ||
fieldType == Trade::SceneFieldType::UnsignedByte)
outputFieldType = Trade::SceneFieldType::UnsignedInt;
else if(fieldType == Trade::SceneFieldType::Int ||
fieldType == Trade::SceneFieldType::Short ||
fieldType == Trade::SceneFieldType::Byte)
outputFieldType = Trade::SceneFieldType::Int;
else CORRADE_ASSERT_UNREACHABLE("SceneTools::mapIndexField(): unsupported field type" << fieldType,
(Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}}));
fields[i] = Trade::SceneFieldData{scene.fieldName(i),
scene.mapping(i), outputFieldType,
Containers::StridedArrayView2D<const char>{{nullptr, ~std::size_t{}}, {scene.fieldSize(i), 4}},
/* We aren't removing any field entries from the scene nor
modifying the mapping in any way, so the flags can be passed
through in full */
scene.fieldFlags(i)}; // TODO test flags
/* Otherwise grab the field in full. This will also convert offset-only
fields to absolute. */
} else fields[i] = scene.fieldData(i);
}
/* Create a new SceneData out of the unpacked index field and all others,
unpack its data into the placeholder location */
Trade::SceneData unpacked = combineFields(scene.mappingType(), scene.mappingBound(), fields);
if(fieldType == Trade::SceneFieldType::UnsignedInt)
Math::castInto(scene.field<UnsignedInt>(fieldId),
unpacked.mutableField<UnsignedInt>(fieldId));
else if(fieldType == Trade::SceneFieldType::UnsignedShort)
Math::castInto(scene.field<UnsignedShort>(fieldId),
unpacked.mutableField<UnsignedInt>(fieldId));
else if(fieldType == Trade::SceneFieldType::UnsignedByte)
Math::castInto(scene.field<UnsignedByte>(fieldId),
unpacked.mutableField<UnsignedInt>(fieldId));
else if(fieldType == Trade::SceneFieldType::Int)
Math::castInto(scene.field<Int>(fieldId),
unpacked.mutableField<Int>(fieldId));
else if(fieldType == Trade::SceneFieldType::Short)
Math::castInto(scene.field<Short>(fieldId),
unpacked.mutableField<Int>(fieldId));
else if(fieldType == Trade::SceneFieldType::Byte)
Math::castInto(scene.field<Byte>(fieldId),
unpacked.mutableField<Int>(fieldId));
else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
mapIndexFieldInPlace(unpacked, fieldId, mapping);
return unpacked;
}
Trade::SceneData mapIndexField(const Trade::SceneData& scene, const Trade::SceneField field, const Containers::StridedArrayView1D<const UnsignedInt>& mapping) {
const Containers::Optional<UnsignedInt> fieldId = scene.findFieldId(field);
CORRADE_ASSERT(fieldId,
"SceneTools::mapIndexField(): field" << field << "not found",
(Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}}));
return mapIndexField(scene, *fieldId, mapping);
}
Trade::SceneData mapIndexField(Trade::SceneData&& scene, const UnsignedInt fieldId, const Containers::StridedArrayView1D<const UnsignedInt>& mapping) {
CORRADE_ASSERT(fieldId < scene.fieldCount(),
"SceneTools::mapIndexField(): index" << fieldId << "out of range for" << scene.fieldCount() << "fields",
(Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}}));
/* Perform the operation in-place, if we can transfewr the ownership and
have the field in the target format already. There's currently no way to
create a SceneData that's Owned but not Mutable so check for Owned is
enough. */
if(scene.dataFlags() & Trade::DataFlag::Owned &&
(scene.fieldType(fieldId) == Trade::SceneFieldType::UnsignedInt ||
scene.fieldType(fieldId) == Trade::SceneFieldType::Int))
{
mapIndexFieldInPlace(scene, fieldId, mapping);
return Utility::move(scene);
}
/* Otherwise delegate to the function that does all the copying and format
expansion */
return mapIndexField(scene, fieldId, mapping);
}
Trade::SceneData mapIndexField(Trade::SceneData&& scene, const Trade::SceneField field, const Containers::StridedArrayView1D<const UnsignedInt>& mapping) {
const Containers::Optional<UnsignedInt> fieldId = scene.findFieldId(field);
CORRADE_ASSERT(fieldId,
"SceneTools::mapIndexField(): field" << field << "not found",
(Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}}));
// TODO test that it gets actually moved
return mapIndexField(Utility::move(scene), *fieldId, mapping);
}
namespace {
template<class T> void mapImplementation(const Containers::StridedArrayView1D<T>& field, const Containers::StridedArrayView1D<const UnsignedInt>& mapping) {
for(T& i: field) {
CORRADE_ASSERT(i < mapping.size(),
"SceneTools::mapIndexFieldInPlace(): index" << i << "out of range for" << mapping.size() << "mapping values", );
CORRADE_ASSERT(mapping[i] < (1ull << sizeof(T)*8),
"SceneTools::mapIndexFieldInPlace(): mapping value" << mapping[i] << "not representable in" << Trade::Implementation::SceneFieldTypeFor<T>::type(), );
i = mapping[i];
}
}
template<class T> void mapSignedImplementation(const Containers::StridedArrayView1D<T>& field, const Containers::StridedArrayView1D<const UnsignedInt>& mapping) {
for(T& i: field) {
if(i == T(-1))
continue;
CORRADE_ASSERT(UnsignedInt(i) < mapping.size(),
"SceneTools::mapIndexFieldInPlace(): index" << i << "out of range for" << mapping.size() << "mapping values", );
CORRADE_ASSERT(mapping[i] >= 0 && mapping[i] < (1u << (sizeof(T)*8 - 1)),
"SceneTools::mapIndexFieldInPlace(): mapping value" << mapping[i] << "not representable in" << Trade::Implementation::SceneFieldTypeFor<T>::type(), );
i = T(mapping[i]);
}
}
}
void mapIndexFieldInPlace(Trade::SceneData& scene, const UnsignedInt fieldId, const Containers::StridedArrayView1D<const UnsignedInt>& mapping) {
CORRADE_ASSERT(fieldId < scene.fieldCount(),
"SceneTools::mapIndexFieldInPlace(): index" << fieldId << "out of range for" << scene.fieldCount() << "fields", );
CORRADE_ASSERT(scene.dataFlags() & Trade::DataFlag::Mutable,
"SceneTools::mapIndexFieldInPlace(): data not mutable", );
CORRADE_ASSERT(scene.fieldArraySize(fieldId) == 0,
"SceneTools::mapIndexFieldInPlace(): array field mapping isn't supported", );
const Trade::SceneFieldType fieldType = scene.fieldType(fieldId);
if(fieldType == Trade::SceneFieldType::UnsignedInt)
mapImplementation(scene.mutableField<UnsignedInt>(fieldId), mapping);
else if(fieldType == Trade::SceneFieldType::UnsignedShort)
mapImplementation(scene.mutableField<UnsignedShort>(fieldId), mapping);
else if(fieldType == Trade::SceneFieldType::UnsignedByte)
mapImplementation(scene.mutableField<UnsignedByte>(fieldId), mapping);
else if(fieldType == Trade::SceneFieldType::Int)
mapSignedImplementation(scene.mutableField<Int>(fieldId), mapping);
else if(fieldType == Trade::SceneFieldType::Short)
mapSignedImplementation(scene.mutableField<Short>(fieldId), mapping);
else if(fieldType == Trade::SceneFieldType::Byte)
mapSignedImplementation(scene.mutableField<Byte>(fieldId), mapping);
else CORRADE_ASSERT_UNREACHABLE("SceneTools::mapIndexFieldInPlace(): unsupported field type" << fieldType, );
}
void mapIndexFieldInPlace(Trade::SceneData& scene, const Trade::SceneField field, const Containers::StridedArrayView1D<const UnsignedInt>& mapping) {
const Containers::Optional<UnsignedInt> fieldId = scene.findFieldId(field);
CORRADE_ASSERT(fieldId,
"SceneTools::mapIndexFieldInPlace(): field" << field << "not found", );
return mapIndexFieldInPlace(scene, *fieldId, mapping);
}
}}

145
src/Magnum/SceneTools/Map.h

@ -0,0 +1,145 @@
#ifndef Magnum_SceneTools_Map_h
#define Magnum_SceneTools_Map_h
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021, 2022, 2023 Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
/** @file
* @brief Function @ref Magnum::SceneTools::mapIndexField(), @ref Magnum::SceneTools::mapIndexFieldInPlace()
* @m_since_latest
*/
#include "Magnum/SceneTools/visibility.h"
#include "Magnum/Trade/Trade.h"
namespace Magnum { namespace SceneTools {
/**
@brief Map an index field in a scene
@m_since_latest
Maps a field containing data indices, such as @ref Trade::SceneField::Mesh or
@ref Trade::SceneField::Camera, to different indices by iterating through the
field at index @p fieldId and replacing a particular @cpp value @ce with
@cpp mapping[value] @ce. If the field has a signed type (such as is the case
with @ref Trade::SceneField::MeshMaterial), @cpp -1 @ce is treated as an
"unset" value and preserved verbatim.
Expects that @p fieldId is less than @ref Trade::SceneData::fieldCount(), the
field is @ref Trade::SceneFieldType::UnsignedInt,
@relativeref{Trade::SceneFieldType,Int}, @relativeref{Trade::SceneFieldType,UnsignedShort},
@relativeref{Trade::SceneFieldType,Short},
@relativeref{Trade::SceneFieldType,UnsignedByte} or
@relativeref{Trade::SceneFieldType,Byte} and is not an array, and that the
@p mapping array is large enough to cover all field values.
The output field is always a @ref Trade::SceneFieldType::UnsignedInt if the
input type is unsigned and @ref Trade::SceneFieldType::Int if it's signed. See
also @ref mapIndexField(Trade::SceneData&&, UnsignedInt, const Containers::StridedArrayView1D<const UnsignedInt>&)
for a potentially more efficient operation instead of always performing a full
copy, you can also do an in-place mapping using
@ref mapIndexFieldInPlace(Trade::SceneData&, UnsignedInt, const Containers::StridedArrayView1D<const UnsignedInt>&)
which doesn't change the field type but additionally expects that the
@p mapping values don't overflow given type.
*/
MAGNUM_SCENETOOLS_EXPORT Trade::SceneData mapIndexField(const Trade::SceneData& scene, UnsignedInt fieldId, const Containers::StridedArrayView1D<const UnsignedInt>& mapping);
/**
@brief Map a named index field in a scene
@m_since_latest
Translates @p field to a field ID using @ref Trade::SceneData::fieldId() and
delegates to @ref mapIndexField(const Trade::SceneData&, UnsignedInt, const Containers::StridedArrayView1D<const UnsignedInt>&).
The @p field is expected to exist in @p scene.
*/
MAGNUM_SCENETOOLS_EXPORT Trade::SceneData mapIndexField(const Trade::SceneData& scene, Trade::SceneField field, const Containers::StridedArrayView1D<const UnsignedInt>& mapping);
/**
@brief Map an index field in a scene
@m_since_latest
Compared to @ref mapIndexField(const Trade::SceneData&, UnsignedInt, const Containers::StridedArrayView1D<const UnsignedInt>&)
this function can perform the mapping in-place, transferring the data ownership
to the returned instance if the data is owned and mutable and the field at
index @p fieldId is @ref Trade::SceneFieldType::UnsignedInt or
@ref Trade::SceneFieldType::Int.
*/
MAGNUM_SCENETOOLS_EXPORT Trade::SceneData mapIndexField(Trade::SceneData&& scene, UnsignedInt fieldId, const Containers::StridedArrayView1D<const UnsignedInt>& mapping);
/**
@brief Map a named index field in a scene
@m_since_latest
Translates @p field to a field ID using @ref Trade::SceneData::fieldId() and
delegates to @ref mapIndexField(Trade::SceneData&&, UnsignedInt, const Containers::StridedArrayView1D<const UnsignedInt>&).
The @p field is expected to exist in @p scene.
*/
MAGNUM_SCENETOOLS_EXPORT Trade::SceneData mapIndexField(Trade::SceneData&& scene, Trade::SceneField field, const Containers::StridedArrayView1D<const UnsignedInt>& mapping);
/**
@brief Map an index field in a scene in-place
@param[in,out] scene Scene
@param[in] fieldId Field to map
@param[in] mapping Index value mapping
@m_since_latest
Maps a field containing data indices, such as @ref Trade::SceneField::Mesh or
@ref Trade::SceneField::Camera, to different indices by iterating through the
field at index @p fieldId and replacing a particular @cpp value @ce with
@cpp mapping[value] @ce. If the field has a signed type (such as is the case
with @ref Trade::SceneField::MeshMaterial), @cpp -1 @ce is treated as an
"unset" value and preserved verbatim.
Expects that the @p scene has mutable data, @p fieldId is less than
@ref Trade::SceneData::fieldCount(), the field is
@ref Trade::SceneFieldType::UnsignedInt,
@relativeref{Trade::SceneFieldType,Int}, @relativeref{Trade::SceneFieldType,UnsignedShort},
@relativeref{Trade::SceneFieldType,Short},
@relativeref{Trade::SceneFieldType,UnsignedByte} or
@relativeref{Trade::SceneFieldType,Byte} and is not an array, the @p mapping
array is large enough to cover all field values and the @p mapping values don't
overflow given type.
If you need to map to a larger index range that doesn't fit into the original
field type, use @ref mapIndexField(const Trade::SceneData&, UnsignedInt, const Containers::StridedArrayView1D<const UnsignedInt>&) instead.
@see @ref Trade::SceneData::dataFlags()
*/
MAGNUM_SCENETOOLS_EXPORT void mapIndexFieldInPlace(Trade::SceneData& scene, UnsignedInt fieldId, const Containers::StridedArrayView1D<const UnsignedInt>& mapping);
/**
@brief Map a named index field in a scene in-place
@param[in,out] scene Scene
@param[in] field Field to map
@param[in] mapping Index value mapping
@m_since_latest
Translates @p field to a field ID using @ref Trade::SceneData::fieldId() and
delegates to @ref mapIndexFieldInPlace(Trade::SceneData&, UnsignedInt, const Containers::StridedArrayView1D<const UnsignedInt>&).
The @p field is expected to exist in @p scene.
*/
MAGNUM_SCENETOOLS_EXPORT void mapIndexFieldInPlace(Trade::SceneData& scene, Trade::SceneField field, const Containers::StridedArrayView1D<const UnsignedInt>& mapping);
}}
#endif

1
src/Magnum/SceneTools/Test/CMakeLists.txt

@ -55,6 +55,7 @@ corrade_add_test(SceneToolsCopyTest CopyTest.cpp LIBRARIES MagnumSceneTools)
corrade_add_test(SceneToolsConvertToSingleFunc___Test ConvertToSingleFunctionObjectsTest.cpp LIBRARIES MagnumSceneToolsTestLib) corrade_add_test(SceneToolsConvertToSingleFunc___Test ConvertToSingleFunctionObjectsTest.cpp LIBRARIES MagnumSceneToolsTestLib)
corrade_add_test(SceneToolsFilterTest FilterTest.cpp LIBRARIES MagnumSceneToolsTestLib) corrade_add_test(SceneToolsFilterTest FilterTest.cpp LIBRARIES MagnumSceneToolsTestLib)
corrade_add_test(SceneToolsHierarchyTest HierarchyTest.cpp LIBRARIES MagnumSceneToolsTestLib) corrade_add_test(SceneToolsHierarchyTest HierarchyTest.cpp LIBRARIES MagnumSceneToolsTestLib)
corrade_add_test(SceneToolsMapTest MapTest.cpp LIBRARIES MagnumSceneToolsTestLib)
corrade_add_test(SceneToolsSceneConverterImple___Test SceneConverterImplementationTest.cpp corrade_add_test(SceneToolsSceneConverterImple___Test SceneConverterImplementationTest.cpp
LIBRARIES MagnumSceneTools LIBRARIES MagnumSceneTools

761
src/Magnum/SceneTools/Test/MapTest.cpp

@ -0,0 +1,761 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021, 2022, 2023 Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include <sstream> /** @todo remove once Debug is stream-free */
#include <Corrade/Containers/StridedBitArrayView.h>
#include <Corrade/Containers/StringIterable.h>
#include <Corrade/Containers/StringView.h>
#include <Corrade/Containers/StringStl.h> /** @todo remove once Debug is stream-free */
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/TestSuite/Compare/Container.h>
#include <Corrade/TestSuite/Compare/String.h>
#include <Corrade/Utility/Algorithms.h>
#include <Corrade/Utility/DebugStl.h> /** @todo remove once Debug is stream-free */
#include "Magnum/Math/TypeTraits.h"
#include "Magnum/SceneTools/Map.h"
#include "Magnum/Trade/SceneData.h"
namespace Magnum { namespace SceneTools { namespace Test { namespace {
struct MapTest: TestSuite::Tester {
explicit MapTest();
template<class T> void indexField();
template<class T> void indexFieldSigned();
void indexFieldOffsetOnly();
void indexFieldFieldNotFound();
void indexFieldInvalidType();
void indexFieldArrayField();
void indexFieldIndexOutOfBounds();
void indexFieldMappingNotRepresentable();
void indexFieldRvalue();
void indexFieldRvalueSigned();
void indexFieldRvalueNamed();
void indexFieldRvalueNotOwned();
void indexFieldRvalueNotFullType();
};
const struct {
const char* name;
bool inPlace;
bool byName;
} IndexFieldData[]{
{"in place, by ID", true, false},
{"in place, by name", true, true},
{"by ID", false, false},
{"by name", false, true}
};
const struct {
const char* name;
bool byName;
} IndexFieldRvalueData[]{
{"by ID", false},
{"by name", true}
};
MapTest::MapTest() {
addInstancedTests<MapTest>({
&MapTest::indexField<UnsignedInt>,
&MapTest::indexField<UnsignedShort>,
&MapTest::indexField<UnsignedByte>,
&MapTest::indexFieldSigned<Int>,
&MapTest::indexFieldSigned<Short>,
&MapTest::indexFieldSigned<Byte>},
Containers::arraySize(IndexFieldData));
addTests({&MapTest::indexFieldOffsetOnly,
&MapTest::indexFieldFieldNotFound,
&MapTest::indexFieldInvalidType,
&MapTest::indexFieldArrayField,
&MapTest::indexFieldIndexOutOfBounds,
&MapTest::indexFieldMappingNotRepresentable});
addInstancedTests({&MapTest::indexFieldRvalue,
&MapTest::indexFieldRvalueSigned},
Containers::arraySize(IndexFieldRvalueData));
addTests({&MapTest::indexFieldRvalueNotOwned,
&MapTest::indexFieldRvalueNotFullType});
}
template<class> struct IndexFieldTraits;
template<> struct IndexFieldTraits<UnsignedInt> {
enum: UnsignedInt { Max = 0xffffffffu };
};
template<> struct IndexFieldTraits<Int> {
enum: Int { Max = 0x7fffffffu };
};
template<> struct IndexFieldTraits<UnsignedShort> {
enum: UnsignedShort { Max = 0xffff };
};
template<> struct IndexFieldTraits<Short> {
enum: Short { Max = 0x7fff };
};
template<> struct IndexFieldTraits<UnsignedByte> {
enum: UnsignedByte { Max = 0xff };
};
template<> struct IndexFieldTraits<Byte> {
enum: Byte { Max = 0x7f };
};
template<class T> void MapTest::indexField() {
auto&& data = IndexFieldData[testCaseInstanceId()];
setTestCaseDescription(data.name);
setTestCaseTemplateName(Math::TypeTraits<T>::name());
struct {
UnsignedLong parentMapping[5];
Int parent[5];
UnsignedLong meshMaterialMapping[4];
T mesh[4];
Short custom[4][2];
} sceneData[]{{
{0, 11, 22, 33, 44},
{-1, -1, 1, 4, 0},
{0, 33, 2, 2},
{5, 9, 1, 0}, /* this one gets mapped */
{{9, 2}, {-1, 3}, {5, 6}, {0, 1}},
}};
Trade::SceneData scene{Trade::SceneMappingType::UnsignedLong, 5, Trade::DataFlag::Mutable, sceneData, {
Trade::SceneFieldData{Trade::SceneField::Parent,
Containers::arrayView(sceneData->parentMapping),
Containers::arrayView(sceneData->parent)},
Trade::SceneFieldData{Trade::SceneField::Mesh,
Containers::arrayView(sceneData->meshMaterialMapping),
Containers::arrayView(sceneData->mesh),
/* Verify that the flags get preserved */
Trade::SceneFieldFlag::MultiEntry},
/* Verify that array fields are supported in non-mapped fields */
Trade::SceneFieldData{Trade::sceneFieldCustom(1),
Trade::SceneMappingType::UnsignedLong,
Containers::arrayView(sceneData->meshMaterialMapping),
Trade::SceneFieldType::Short,
Containers::arrayView(sceneData->custom), 2},
}};
/* The 0xffffffffu values shouldn't be used for anything */
const UnsignedInt mapping[]{
12, 0, 0xffffffffu, 0xffffffffu, 0xffffffffu,
/* If not doing an in-place mapping, the output doesn't have to fit
into the original type */
data.inPlace ? UnsignedInt(IndexFieldTraits<T>::Max) : 1000000u,
0xffffffffu, 0xffffffffu, 0xffffffffu, 3
};
Trade::SceneData output{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}};
if(data.inPlace) {
if(data.byName)
mapIndexFieldInPlace(scene, Trade::SceneField::Mesh, mapping);
else
mapIndexFieldInPlace(scene, 1, mapping);
} else {
if(data.byName)
output = mapIndexField(scene, Trade::SceneField::Mesh, mapping);
else
output = mapIndexField(scene, 1, mapping);
}
const Trade::SceneData& result = data.inPlace ? scene : output;
/* Mapping should stay untouched */
CORRADE_COMPARE(result.mappingBound(), 5);
CORRADE_COMPARE(result.mappingType(), Trade::SceneMappingType::UnsignedLong);
CORRADE_COMPARE_AS(result.mapping<UnsignedLong>(0), Containers::arrayView<UnsignedLong>({
0, 11, 22, 33, 44
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(result.mapping<UnsignedLong>(1), Containers::arrayView<UnsignedLong>({
0, 33, 2, 2
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(result.mapping<UnsignedLong>(2), Containers::arrayView<UnsignedLong>({
0, 33, 2, 2
}), TestSuite::Compare::Container);
/* All fields except the mesh should stay the same as before. With the
in-place variant the type should stay the same, otherwise expanded to
32-bit. */
CORRADE_COMPARE_AS(result.field<Int>(0), Containers::arrayView<Int>({
-1, -1, 1, 4, 0
}), TestSuite::Compare::Container);
if(data.inPlace) {
CORRADE_COMPARE(result.fieldType(1), Trade::Implementation::SceneFieldTypeFor<T>::type());
CORRADE_COMPARE_AS(result.field<T>(1), Containers::arrayView<T>({
IndexFieldTraits<T>::Max, 3, 0, 12
}), TestSuite::Compare::Container);
} else {
CORRADE_COMPARE(result.fieldType(1), Trade::SceneFieldType::UnsignedInt);
CORRADE_COMPARE_AS(result.field<UnsignedInt>(1), Containers::arrayView<UnsignedInt>({
1000000, 3, 0, 12
}), TestSuite::Compare::Container);
}
/* The flags should be preserved */
CORRADE_COMPARE(result.fieldFlags(1), Trade::SceneFieldFlag::MultiEntry);
/* Non-mapped array field should be copied as-is without any assert */
CORRADE_COMPARE_AS((result.field<Short[]>(2).transposed<0, 1>()[0]), Containers::arrayView<Short>({
9, -1, 5, 0
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS((result.field<Short[]>(2).transposed<0, 1>()[1]), Containers::arrayView<Short>({
2, 3, 6, 1
}), TestSuite::Compare::Container);
}
template<class T> void MapTest::indexFieldSigned() {
auto&& data = IndexFieldData[testCaseInstanceId()];
setTestCaseDescription(data.name);
setTestCaseTemplateName(Math::TypeTraits<T>::name());
/* Similar to indexField(), except that here the meshMaterial gets mapped
instead */
struct {
UnsignedByte parentMapping[5];
Int parent[5];
UnsignedByte meshMaterialMapping[4];
T meshMaterial[4];
UnsignedShort mesh[4];
} sceneData[]{{
{0, 11, 22, 33, 44},
{-1, -1, 1, 4, 0},
{0, 33, 2, 2},
{9, -1, 5, 1}, /* this one gets mapped */
{5, 9, 1, 0},
}};
Trade::SceneData scene{Trade::SceneMappingType::UnsignedByte, 55, Trade::DataFlag::Mutable, sceneData, {
Trade::SceneFieldData{Trade::SceneField::Parent,
Containers::arrayView(sceneData->parentMapping),
Containers::arrayView(sceneData->parent)},
Trade::SceneFieldData{Trade::SceneField::MeshMaterial,
Containers::arrayView(sceneData->meshMaterialMapping),
Containers::arrayView(sceneData->meshMaterial)},
Trade::SceneFieldData{Trade::SceneField::Mesh,
Containers::arrayView(sceneData->meshMaterialMapping),
Containers::arrayView(sceneData->mesh)},
}};
/* The 0xffffffffu values shouldn't be used for anything */
const UnsignedInt mapping[]{
0xffffffffu, 12, 0xffffffffu, 0xffffffffu, 0xffffffffu,
IndexFieldTraits<T>::Max, 0xffffffffu, 0xffffffffu, 0xffffffffu, 3
};
Trade::SceneData output{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}};
if(data.inPlace) {
if(data.byName)
mapIndexFieldInPlace(scene, Trade::SceneField::MeshMaterial, mapping);
else
mapIndexFieldInPlace(scene, 1, mapping);
} else {
if(data.byName)
output = mapIndexField(scene, Trade::SceneField::MeshMaterial, mapping);
else
output = mapIndexField(scene, 1, mapping);
}
const Trade::SceneData& result = data.inPlace ? scene : output;
/* Mapping should stay untouched */
CORRADE_COMPARE(result.mappingBound(), 55);
CORRADE_COMPARE(result.mappingType(), Trade::SceneMappingType::UnsignedByte);
CORRADE_COMPARE_AS(result.mapping<UnsignedByte>(0), Containers::arrayView<UnsignedByte>({
0, 11, 22, 33, 44
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(result.mapping<UnsignedByte>(1), Containers::arrayView<UnsignedByte>({
0, 33, 2, 2
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(result.mapping<UnsignedByte>(2), Containers::arrayView<UnsignedByte>({
0, 33, 2, 2
}), TestSuite::Compare::Container);
/* All fields except the mesh material should stay the same as before. With
the in-place variant the type should stay the same, otherwise expanded
to 32-bit. */
CORRADE_COMPARE_AS(result.field<Int>(0), Containers::arrayView<Int>({
-1, -1, 1, 4, 0
}), TestSuite::Compare::Container);
if(data.inPlace) {
CORRADE_COMPARE(result.fieldType(1), Trade::Implementation::SceneFieldTypeFor<T>::type());
CORRADE_COMPARE_AS(result.field<T>(1), Containers::arrayView<T>({
3, -1, IndexFieldTraits<T>::Max, 12
}), TestSuite::Compare::Container);
} else {
CORRADE_COMPARE(result.fieldType(1), Trade::SceneFieldType::Int);
CORRADE_COMPARE_AS(result.field<Int>(1), Containers::arrayView<Int>({
3, -1, IndexFieldTraits<T>::Max, 12
}), TestSuite::Compare::Container);
}
CORRADE_COMPARE_AS(result.field<UnsignedShort>(2), Containers::arrayView<UnsignedShort>({
5, 9, 1, 0
}), TestSuite::Compare::Container);
}
void MapTest::indexFieldOffsetOnly() {
/* Subset of indexField() with the mapped field being specified as
offset-only. Should "just work" without any special treatment needed in
the implementation. */
struct SceneData {
UnsignedShort meshMaterialMapping[4];
Byte meshMaterial[4];
UnsignedShort mesh[4];
} sceneData[]{{
{0, 33, 2, 2},
{9, -1, 5, 1},
{5, 9, 1, 0}, /* this one gets mapped */
}};
Trade::SceneData scene{Trade::SceneMappingType::UnsignedShort, 5, Trade::DataFlag::Mutable, sceneData, {
Trade::SceneFieldData{Trade::SceneField::MeshMaterial,
Containers::arrayView(sceneData->meshMaterialMapping),
Containers::arrayView(sceneData->meshMaterial)},
Trade::SceneFieldData{Trade::SceneField::Mesh, 4,
Trade::SceneMappingType::UnsignedShort, offsetof(SceneData, meshMaterialMapping), sizeof(UnsignedShort),
Trade::SceneFieldType::UnsignedShort, offsetof(SceneData, mesh), sizeof(UnsignedShort)},
}};
/* The 0xffffffffu values shouldn't be used for anything */
const UnsignedInt mapping[]{
12, 0, 0xffffffffu, 0xffffffffu, 0xffffffffu,
0xffff, 0xffffffffu, 0xffffffffu, 0xffffffffu, 3
};
mapIndexFieldInPlace(scene, Trade::SceneField::Mesh, mapping);
CORRADE_COMPARE_AS(scene.field<UnsignedShort>(1), Containers::arrayView<UnsignedShort>({
0xffff, 3, 0, 12
}), TestSuite::Compare::Container);
}
void MapTest::indexFieldFieldNotFound() {
CORRADE_SKIP_IF_NO_ASSERT();
Trade::SceneData scene{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {
Trade::SceneFieldData{Trade::SceneField::Parent,
Trade::SceneMappingType::UnsignedInt, nullptr,
Trade::SceneFieldType::Int, nullptr},
Trade::SceneFieldData{Trade::SceneField::Mesh,
Trade::SceneMappingType::UnsignedInt, nullptr,
Trade::SceneFieldType::UnsignedInt, nullptr},
}};
UnsignedInt mapping[5]{};
std::ostringstream out;
Error redirectError{&out};
mapIndexField(scene, 2, mapping);
mapIndexFieldInPlace(scene, 2, mapping);
mapIndexField(scene, Trade::SceneField::MeshMaterial, mapping);
mapIndexFieldInPlace(scene, Trade::SceneField::MeshMaterial, mapping);
CORRADE_COMPARE_AS(out.str(),
"SceneTools::mapIndexField(): index 2 out of range for 2 fields\n"
"SceneTools::mapIndexFieldInPlace(): index 2 out of range for 2 fields\n"
"SceneTools::mapIndexField(): field Trade::SceneField::MeshMaterial not found\n"
"SceneTools::mapIndexFieldInPlace(): field Trade::SceneField::MeshMaterial not found\n",
TestSuite::Compare::String);
}
void MapTest::indexFieldInvalidType() {
CORRADE_SKIP_IF_NO_ASSERT();
Trade::SceneData scene{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {
Trade::SceneFieldData{Trade::SceneField::Mesh,
Trade::SceneMappingType::UnsignedInt, nullptr,
Trade::SceneFieldType::UnsignedInt, nullptr},
Trade::SceneFieldData{Trade::SceneField::Parent,
Trade::SceneMappingType::UnsignedInt, nullptr,
Trade::SceneFieldType::Long, nullptr},
}};
UnsignedInt mapping[5]{};
std::ostringstream out;
Error redirectError{&out};
mapIndexField(scene, 1, mapping);
mapIndexFieldInPlace(scene, 1, mapping);
CORRADE_COMPARE_AS(out.str(),
"SceneTools::mapIndexField(): unsupported field type Trade::SceneFieldType::Long\n"
"SceneTools::mapIndexFieldInPlace(): unsupported field type Trade::SceneFieldType::Long\n",
TestSuite::Compare::String);
}
void MapTest::indexFieldArrayField() {
CORRADE_SKIP_IF_NO_ASSERT();
Trade::SceneData scene{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {
Trade::SceneFieldData{Trade::SceneField::Mesh,
Trade::SceneMappingType::UnsignedInt, nullptr,
Trade::SceneFieldType::UnsignedInt, nullptr},
Trade::SceneFieldData{Trade::sceneFieldCustom(0x1337),
Trade::SceneMappingType::UnsignedInt, nullptr,
Trade::SceneFieldType::Byte, nullptr, 3},
}};
UnsignedInt mapping[5]{};
std::ostringstream out;
Error redirectError{&out};
mapIndexField(scene, 1, mapping);
mapIndexFieldInPlace(scene, 1, mapping);
CORRADE_COMPARE_AS(out.str(),
"SceneTools::mapIndexField(): array field mapping isn't supported\n"
"SceneTools::mapIndexFieldInPlace(): array field mapping isn't supported\n",
TestSuite::Compare::String);
}
void MapTest::indexFieldIndexOutOfBounds() {
CORRADE_SKIP_IF_NO_ASSERT();
const struct {
UnsignedShort meshMaterialMapping[4];
Byte meshMaterial[4];
UnsignedShort mesh[4];
} sceneData[]{{
{},
{5, -1, 9, -2},
{5, 10, 1, 0},
}};
Trade::SceneData scene{Trade::SceneMappingType::UnsignedShort, 5, {}, sceneData, {
Trade::SceneFieldData{Trade::SceneField::Mesh,
Containers::arrayView(sceneData->meshMaterialMapping),
Containers::arrayView(sceneData->mesh)},
Trade::SceneFieldData{Trade::SceneField::MeshMaterial,
Containers::arrayView(sceneData->meshMaterialMapping),
Containers::arrayView(sceneData->meshMaterial)},
}};
const UnsignedInt mapping9[9]{};
const UnsignedInt mapping10[10]{};
std::ostringstream out;
Error redirectError{&out};
mapIndexField(scene, Trade::SceneField::MeshMaterial, mapping10);
mapIndexField(scene, Trade::SceneField::MeshMaterial, mapping9);
mapIndexField(scene, Trade::SceneField::Mesh, mapping10);
CORRADE_COMPARE_AS(out.str(),
"SceneTools::mapIndexFieldInPlace(): index -2 out of range for 10 mapping values\n"
"SceneTools::mapIndexFieldInPlace(): index 9 out of range for 9 mapping values\n"
"SceneTools::mapIndexFieldInPlace(): index 10 out of range for 10 mapping values\n",
TestSuite::Compare::String);
}
void MapTest::indexFieldMappingNotRepresentable() {
CORRADE_SKIP_IF_NO_ASSERT();
struct {
UnsignedShort mapping[4];
UnsignedShort mesh[4];
UnsignedByte light[4];
Int custom1[4];
Short custom2[4];
Byte meshMaterial[4];
} sceneData[]{{
{},
{0, 4, 3, 1},
{0, 4, 3, 1},
{0, -1, 3, 1},
{0, -1, 2, 1},
{0, -1, 0, 1},
}};
Trade::SceneData scene{Trade::SceneMappingType::UnsignedShort, 1, Trade::DataFlag::Mutable, sceneData, {
Trade::SceneFieldData{Trade::SceneField::Mesh,
Containers::arrayView(sceneData->mapping),
Containers::arrayView(sceneData->mesh)},
Trade::SceneFieldData{Trade::SceneField::Light,
Containers::arrayView(sceneData->mapping),
Containers::arrayView(sceneData->light)},
Trade::SceneFieldData{Trade::sceneFieldCustom(1),
Containers::arrayView(sceneData->mapping),
Containers::arrayView(sceneData->custom1)},
Trade::SceneFieldData{Trade::sceneFieldCustom(2),
Containers::arrayView(sceneData->mapping),
Containers::arrayView(sceneData->custom2)},
Trade::SceneFieldData{Trade::SceneField::MeshMaterial,
Containers::arrayView(sceneData->mapping),
Containers::arrayView(sceneData->meshMaterial)},
}};
UnsignedInt mappingUnsigned[]{
/* Index 1 is too large for UnsignedByte, 2 isn't used, 3 is
too large for UnsignedShort */
1, 0x100, 0xffffffffu, 0x10000, 3
};
UnsignedInt mappingSigned[]{
/* Index 1 is too large for a Byte, 3 too large for Int, 2 too large
for Short */
1, 0x80, 0x8000, 0x80000000u
};
/* These should all be okay as they expand to 32 bits */
mapIndexField(scene, Trade::SceneField::Mesh, mappingUnsigned);
mapIndexField(scene, Trade::SceneField::Light, mappingUnsigned);
mapIndexField(scene, Trade::sceneFieldCustom(2), mappingSigned);
mapIndexField(scene, Trade::SceneField::MeshMaterial, mappingSigned);
std::ostringstream out;
Error redirectError{&out};
mapIndexFieldInPlace(scene, Trade::SceneField::Mesh, mappingUnsigned);
mapIndexFieldInPlace(scene, Trade::SceneField::Light, mappingUnsigned);
/* This one expands to 32 bits but is still signed which isn't enough */
mapIndexField(scene, Trade::sceneFieldCustom(1), mappingSigned);
mapIndexFieldInPlace(scene, Trade::sceneFieldCustom(1), mappingSigned);
mapIndexFieldInPlace(scene, Trade::sceneFieldCustom(2), mappingSigned);
mapIndexFieldInPlace(scene, Trade::SceneField::MeshMaterial, mappingSigned);
CORRADE_COMPARE_AS(out.str(),
"SceneTools::mapIndexFieldInPlace(): mapping value 65536 not representable in Trade::SceneFieldType::UnsignedShort\n"
"SceneTools::mapIndexFieldInPlace(): mapping value 65536 not representable in Trade::SceneFieldType::UnsignedByte\n"
"SceneTools::mapIndexFieldInPlace(): mapping value 2147483648 not representable in Trade::SceneFieldType::Int\n"
"SceneTools::mapIndexFieldInPlace(): mapping value 2147483648 not representable in Trade::SceneFieldType::Int\n"
"SceneTools::mapIndexFieldInPlace(): mapping value 32768 not representable in Trade::SceneFieldType::Short\n"
"SceneTools::mapIndexFieldInPlace(): mapping value 128 not representable in Trade::SceneFieldType::Byte\n",
TestSuite::Compare::String);
}
void MapTest::indexFieldRvalue() {
auto&& data = IndexFieldRvalueData[testCaseInstanceId()];
setTestCaseDescription(data.name);
struct Data {
UnsignedByte mapping[4];
Short meshMaterial[4];
UnsignedInt mesh[4];
};
Containers::Array<char> sceneData{NoInit, sizeof(Data)};
Containers::StridedArrayView1D<Data> view = Containers::arrayCast<Data>(sceneData);
Utility::copy({{
{77, 33, 44, 66},
{2, -1, 0, 1},
{3, 4, 1, 0},
}}, view);
Trade::SceneData scene{Trade::SceneMappingType::UnsignedByte, 88, Utility::move(sceneData), {
Trade::SceneFieldData{Trade::SceneField::MeshMaterial,
Containers::arrayView(view[0].mapping),
Containers::arrayView(view[0].meshMaterial)},
Trade::SceneFieldData{Trade::SceneField::Mesh,
Containers::arrayView(view[0].mapping),
Containers::arrayView(view[0].mesh)},
}};
const Trade::SceneFieldData* originalFields = scene.fieldData();
const UnsignedInt mapping[]{
15, 16, 0xffffffffu, 7, 9
};
Trade::SceneData mapped = data.byName ?
mapIndexField(Utility::move(scene), Trade::SceneField::Mesh, mapping) :
mapIndexField(Utility::move(scene), 1, mapping);
/* Mapping should stay untouched */
CORRADE_COMPARE(mapped.mappingBound(), 88);
CORRADE_COMPARE(mapped.mappingType(), Trade::SceneMappingType::UnsignedByte);
CORRADE_COMPARE_AS(mapped.mapping<UnsignedByte>(0), Containers::arrayView<UnsignedByte>({
77, 33, 44, 66
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(mapped.mapping<UnsignedByte>(1), Containers::arrayView<UnsignedByte>({
77, 33, 44, 66
}), TestSuite::Compare::Container);
/* Mesh should be mapped, materials should stay the same as before */
CORRADE_COMPARE_AS(mapped.field<Short>(0), Containers::arrayView<Short>({
2, -1, 0, 1
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(mapped.field<UnsignedInt>(1), Containers::arrayView<UnsignedInt>({
7, 9, 16, 15
}), TestSuite::Compare::Container);
/* Both data should be transferred without any copy */
CORRADE_COMPARE(mapped.data().data(), view.data());
CORRADE_COMPARE(mapped.fieldData().data(), originalFields);
}
void MapTest::indexFieldRvalueSigned() {
auto&& data = IndexFieldRvalueData[testCaseInstanceId()];
setTestCaseDescription(data.name);
struct Data {
UnsignedByte mapping[4];
UnsignedShort mesh[4];
Int meshMaterial[4];
};
Containers::Array<char> sceneData{NoInit, sizeof(Data)};
Containers::StridedArrayView1D<Data> view = Containers::arrayCast<Data>(sceneData);
Utility::copy({{
{77, 33, 44, 66},
{3, 4, 1, 0},
{2, -1, 0, 3},
}}, view);
Trade::SceneData scene{Trade::SceneMappingType::UnsignedByte, 88, Utility::move(sceneData), {
Trade::SceneFieldData{Trade::SceneField::Mesh,
Containers::arrayView(view[0].mapping),
Containers::arrayView(view[0].mesh)},
Trade::SceneFieldData{Trade::SceneField::MeshMaterial,
Containers::arrayView(view[0].mapping),
Containers::arrayView(view[0].meshMaterial)},
}};
const Trade::SceneFieldData* originalFields = scene.fieldData();
const UnsignedInt mapping[]{
15, 0xffffffffu, 16, 7
};
Trade::SceneData mapped = data.byName ?
mapIndexField(Utility::move(scene), Trade::SceneField::MeshMaterial, mapping) :
mapIndexField(Utility::move(scene), 1, mapping);
/* Mapping should stay untouched */
CORRADE_COMPARE(mapped.mappingBound(), 88);
CORRADE_COMPARE(mapped.mappingType(), Trade::SceneMappingType::UnsignedByte);
CORRADE_COMPARE_AS(mapped.mapping<UnsignedByte>(0), Containers::arrayView<UnsignedByte>({
77, 33, 44, 66
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(mapped.mapping<UnsignedByte>(1), Containers::arrayView<UnsignedByte>({
77, 33, 44, 66
}), TestSuite::Compare::Container);
/* Mesh should stay the same as before, materials should be mapped */
CORRADE_COMPARE_AS(mapped.field<UnsignedShort>(0), Containers::arrayView<UnsignedShort>({
3, 4, 1, 0
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(mapped.field<Int>(1), Containers::arrayView<Int>({
16, -1, 15, 7
}), TestSuite::Compare::Container);
/* Both data should be transferred without any copy */
CORRADE_COMPARE(mapped.data().data(), view.data());
CORRADE_COMPARE(mapped.fieldData().data(), originalFields);
}
void MapTest::indexFieldRvalueNotOwned() {
/* Like indexFieldRvalue(), but the data is not owned so it should perform
a copy */
struct {
UnsignedByte mapping[4];
Short meshMaterial[4];
UnsignedInt mesh[4];
} sceneData[]{{
{77, 33, 44, 66},
{2, -1, 0, 1},
{3, 4, 1, 0},
}};
/* Mark the data as Mutable to test it isn't accidentally treated the same
as Owned */
Trade::SceneData scene{Trade::SceneMappingType::UnsignedByte, 88, Trade::DataFlag::Mutable, sceneData, {
Trade::SceneFieldData{Trade::SceneField::MeshMaterial,
Containers::arrayView(sceneData->mapping),
Containers::arrayView(sceneData->meshMaterial)},
Trade::SceneFieldData{Trade::SceneField::Mesh,
Containers::arrayView(sceneData->mapping),
Containers::arrayView(sceneData->mesh)},
}};
const Trade::SceneFieldData* originalFields = scene.fieldData();
const UnsignedInt mapping[]{
15, 16, 0xffffffffu, 7, 9
};
Trade::SceneData mapped = mapIndexField(Utility::move(scene), 1, mapping);
/* Mapping should stay untouched */
CORRADE_COMPARE(mapped.mappingBound(), 88);
CORRADE_COMPARE(mapped.mappingType(), Trade::SceneMappingType::UnsignedByte);
CORRADE_COMPARE_AS(mapped.mapping<UnsignedByte>(0), Containers::arrayView<UnsignedByte>({
77, 33, 44, 66
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(mapped.mapping<UnsignedByte>(1), Containers::arrayView<UnsignedByte>({
77, 33, 44, 66
}), TestSuite::Compare::Container);
/* Mesh should be mapped, materials should stay the same as before */
CORRADE_COMPARE_AS(mapped.field<Short>(0), Containers::arrayView<Short>({
2, -1, 0, 1
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(mapped.field<UnsignedInt>(1), Containers::arrayView<UnsignedInt>({
7, 9, 16, 15
}), TestSuite::Compare::Container);
/* Data should be copied */
CORRADE_VERIFY(mapped.data().data() != static_cast<void*>(sceneData));
CORRADE_VERIFY(mapped.fieldData().data() != originalFields);
}
void MapTest::indexFieldRvalueNotFullType() {
/* Like indexFieldRvalue(), but the field is not a 32-bit type */
struct Data {
UnsignedByte mapping[4];
Int meshMaterial[4];
UnsignedShort mesh[4];
};
Containers::Array<char> sceneData{NoInit, sizeof(Data)};
Containers::StridedArrayView1D<Data> view = Containers::arrayCast<Data>(sceneData);
Utility::copy({{
{77, 33, 44, 66},
{2, -1, 0, 1},
{3, 4, 1, 0},
}}, view);
Trade::SceneData scene{Trade::SceneMappingType::UnsignedByte, 88, Utility::move(sceneData), {
Trade::SceneFieldData{Trade::SceneField::MeshMaterial,
Containers::arrayView(view[0].mapping),
Containers::arrayView(view[0].meshMaterial)},
Trade::SceneFieldData{Trade::SceneField::Mesh,
Containers::arrayView(view[0].mapping),
Containers::arrayView(view[0].mesh)},
}};
const Trade::SceneFieldData* originalFields = scene.fieldData();
const UnsignedInt mapping[]{
15, 16, 0xffffffffu, 7, 9
};
Trade::SceneData mapped = mapIndexField(Utility::move(scene), 1, mapping);
/* Mapping should stay untouched */
CORRADE_COMPARE(mapped.mappingBound(), 88);
CORRADE_COMPARE(mapped.mappingType(), Trade::SceneMappingType::UnsignedByte);
CORRADE_COMPARE_AS(mapped.mapping<UnsignedByte>(0), Containers::arrayView<UnsignedByte>({
77, 33, 44, 66
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(mapped.mapping<UnsignedByte>(1), Containers::arrayView<UnsignedByte>({
77, 33, 44, 66
}), TestSuite::Compare::Container);
/* Mesh should be mapped, materials should stay the same as before. As a
copy is performed, the type is expanded to 32 bits. */
CORRADE_COMPARE_AS(mapped.field<Int>(0), Containers::arrayView<Int>({
2, -1, 0, 1
}), TestSuite::Compare::Container);
CORRADE_COMPARE(mapped.fieldType(1), Trade::SceneFieldType::UnsignedInt);
CORRADE_COMPARE_AS(mapped.field<UnsignedInt>(1), Containers::arrayView<UnsignedInt>({
7, 9, 16, 15
}), TestSuite::Compare::Container);
/* Data should be copied */
CORRADE_VERIFY(mapped.data().data() != static_cast<void*>(sceneData));
CORRADE_VERIFY(mapped.fieldData().data() != originalFields);
}
}}}}
CORRADE_TEST_MAIN(Magnum::SceneTools::Test::MapTest)
Loading…
Cancel
Save