Browse Source

Trade: internal tool for making SceneData with single-function objects.

Needed for backwards compatibility purposes currently, eventually it'll
become a public API in the upcoming SceneTools library.
pull/525/head
Vladimír Vondruš 5 years ago
parent
commit
9cdc5f0e5a
  1. 115
      src/Magnum/Trade/Implementation/sceneTools.h
  2. 132
      src/Magnum/Trade/Test/SceneToolsTest.cpp

115
src/Magnum/Trade/Implementation/sceneTools.h

@ -28,9 +28,11 @@
#include <unordered_map>
#include <Corrade/Containers/ArrayTuple.h>
#include <Corrade/Containers/GrowableArray.h>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Containers/Pair.h>
#include <Corrade/Utility/Algorithms.h>
#include "Magnum/Math/Functions.h"
#include "Magnum/Math/PackingBatch.h"
#include "Magnum/Trade/SceneData.h"
@ -188,6 +190,119 @@ inline SceneData sceneCombine(const SceneObjectType objectType, const UnsignedLo
return SceneData{objectType, objectCount, std::move(outData), std::move(outFields)};
}
/* Creates a SceneData copy where each object has at most one of the fields
listed in the passed array. This is done by enlarging the parents array
and moving extraneous features to new objects that are marked as a child of
the original. No transformations or other fields are added for the new
objects. Fields that are connected together (such as meshes and materials)
are assumed to share the same object mapping with only one of them passed in
the fieldsToConvert array, which will result for all fields from the same
set being reassociated to the new object.
Requies a SceneField::Parent to be present -- otherwise it wouldn't be
possible to know where to attach the new objects. */
/** @todo when published, (again) add an initializer_list overload and turn all
internal asserts into (tested!) message asserts */
inline SceneData sceneConvertToSingleFunctionObjects(const SceneData& scene, Containers::ArrayView<const SceneField> fieldsToConvert, const UnsignedInt newObjectOffset) {
/** @todo assert for really high object counts (where this cast would fail) */
Containers::Array<UnsignedInt> objectAttachmentCount{ValueInit, std::size_t(scene.objectCount())};
for(const SceneField field: fieldsToConvert) {
CORRADE_INTERNAL_ASSERT(field != SceneField::Parent);
/* Skip fields that are not present -- is it's not present, then it
definitely won't be responsible for multi-function objects */
const Containers::Optional<UnsignedInt> fieldId = scene.findFieldId(field);
if(!fieldId) continue;
/** @todo use a statically-allocated array & Into() in a loop instead
once this is more than a private backwards-compatibility utility
where PERF WHATEVER WHO CARES */
for(const UnsignedInt object: scene.objectsAsArray(*fieldId)) {
CORRADE_INTERNAL_ASSERT(object < objectAttachmentCount.size());
++objectAttachmentCount[object];
}
}
UnsignedInt objectsToAdd = 0;
for(const UnsignedInt count: objectAttachmentCount)
if(count > 1) objectsToAdd += count - 1;
/* Ensure we don't overflow the 32-bit object count with the objects to
add. This should also cover the case when the parent field would not be
representable in 32 bits. */
CORRADE_INTERNAL_ASSERT(newObjectOffset + objectsToAdd >= newObjectOffset);
/* Copy the fields over, enlarging them as necessary */
const UnsignedInt parentFieldId = scene.fieldId(SceneField::Parent);
Containers::Array<SceneFieldData> fields{scene.fieldCount()};
for(std::size_t i = 0; i != scene.fieldCount(); ++i) {
const SceneFieldData& field = scene.fieldData(i);
/* If this is a parent, enlarge it for the newly added objects */
if(field.name() == SceneField::Parent) {
/** @todo some nicer constructor for placeholders once this is in
public interest */
fields[i] = SceneFieldData{SceneField::Parent, Containers::ArrayView<const UnsignedInt>{nullptr, std::size_t(field.size() + objectsToAdd)}, Containers::ArrayView<const Int>{nullptr, std::size_t(field.size() + objectsToAdd)}};
/* All other fields are copied as-is */
} else fields[i] = field;
}
/* Combine the fields into a new SceneData */
SceneData out = sceneCombine(SceneObjectType::UnsignedInt, Math::max(scene.objectCount(), UnsignedLong(newObjectOffset) + objectsToAdd), fields);
/* Copy existing parent object/field data to a prefix of the output */
const Containers::StridedArrayView1D<UnsignedInt> outParentObjects = out.mutableObjects<UnsignedInt>(parentFieldId);
const Containers::StridedArrayView1D<Int> outParents = out.mutableField<Int>(parentFieldId);
CORRADE_INTERNAL_ASSERT_OUTPUT(scene.objectsInto(parentFieldId, 0, outParentObjects) == scene.fieldSize(parentFieldId));
CORRADE_INTERNAL_ASSERT_OUTPUT(scene.parentsInto(0, outParents) == scene.fieldSize(parentFieldId));
/* List new objects at the end of the extended parent field */
const Containers::StridedArrayView1D<UnsignedInt> newParentObjects = outParentObjects.suffix(scene.fieldSize(parentFieldId));
const Containers::StridedArrayView1D<Int> newParents = outParents.suffix(scene.fieldSize(parentFieldId));
for(std::size_t i = 0; i != newParentObjects.size(); ++i) {
newParentObjects[i] = newObjectOffset + i;
newParents[i] = -1;
}
/* Clear the objectAttachmentCount array to reuse it below */
/** @todo use a BitArray instead once it exists? */
constexpr UnsignedInt zero[1]{};
Utility::copy(Containers::stridedArrayView(zero).broadcasted<0>(scene.objectCount()), objectAttachmentCount);
/* For objects with multiple fields move the extra fields to newly added
children */
{
std::size_t newParentIndex = 0;
for(const SceneField field: fieldsToConvert) {
const Containers::Optional<UnsignedInt> fieldId = scene.findFieldId(field);
if(!fieldId) continue;
for(UnsignedInt& fieldObject: out.mutableObjects<UnsignedInt>(*fieldId)) {
/* If the object is not new (could happen when an object
mapping array is shared among multiple fields, in which case
it *might* have been updated already to an ID larger than
the mapping array size) and it already has something
attached, then attach the field to a new object and make
that new object a child of the previous one. */
if(fieldObject < objectAttachmentCount.size() && objectAttachmentCount[fieldObject]) {
/* Find an index of the old object and then use that index
to denote the parent of the new object */
newParents[newParentIndex] = out.fieldObjectOffset(parentFieldId, fieldObject);
/* Assign the field to the new object */
fieldObject = newParentObjects[newParentIndex];
/* Move to the next reserved object */
++newParentIndex;
} else ++objectAttachmentCount[fieldObject];
}
}
CORRADE_INTERNAL_ASSERT(newParentIndex == objectsToAdd);
}
return out;
}
}}}
#endif

132
src/Magnum/Trade/Test/SceneToolsTest.cpp

@ -41,6 +41,8 @@ struct SceneToolsTest: TestSuite::Tester {
void combineObjectsShared();
void combineObjectsPlaceholderFieldPlaceholder();
void combineObjectSharedFieldPlaceholder();
void convertToSingleFunctionObjects();
};
struct {
@ -53,6 +55,15 @@ struct {
{"UnsignedLong output", SceneObjectType::UnsignedLong},
};
struct {
const char* name;
UnsignedLong originalObjectCount;
UnsignedLong expectedObjectCount;
} ConvertToSingleFunctionObjectsData[]{
{"original object count smaller than new", 64, 67},
{"original object count larger than new", 96, 96}
};
SceneToolsTest::SceneToolsTest() {
addInstancedTests({&SceneToolsTest::combine},
Containers::arraySize(CombineData));
@ -61,6 +72,9 @@ SceneToolsTest::SceneToolsTest() {
&SceneToolsTest::combineObjectsShared,
&SceneToolsTest::combineObjectsPlaceholderFieldPlaceholder,
&SceneToolsTest::combineObjectSharedFieldPlaceholder});
addInstancedTests({&SceneToolsTest::convertToSingleFunctionObjects},
Containers::arraySize(ConvertToSingleFunctionObjectsData));
}
using namespace Math::Literals;
@ -319,6 +333,124 @@ void SceneToolsTest::combineObjectSharedFieldPlaceholder() {
CORRADE_COMPARE(scene.field(SceneField::MeshMaterial).stride()[0], 4);
}
void SceneToolsTest::convertToSingleFunctionObjects() {
auto&& data = ConvertToSingleFunctionObjectsData[testCaseInstanceId()];
setTestCaseDescription(data.name);
/* Haha now I can use sceneCombine() to conveniently prepare the initial
state here, without having to mess with an ArrayTuple */
const UnsignedShort parentObjects[]{15, 21, 22, 23, 1};
const Byte parents[]{-1, -1, 1, 2, -1};
/* Two objects have two and three mesh assignments respectively, meaning we
need three extra */
const UnsignedShort meshObjects[]{15, 23, 23, 23, 1, 15, 21};
const Containers::Pair<UnsignedInt, Int> meshesMaterials[]{
{6, 4},
{1, 0},
{2, 3},
{4, 2},
{7, 2},
{3, 1},
{5, -1}
};
/* One camera is attached to an object that already has a mesh, meaning we
need a third extra object */
const UnsignedShort cameraObjects[]{22, 1};
const UnsignedInt cameras[]{1, 5};
SceneData original = Implementation::sceneCombine(SceneObjectType::UnsignedShort, data.originalObjectCount, Containers::arrayView({
SceneFieldData{SceneField::Parent, Containers::arrayView(parentObjects), Containers::arrayView(parents)},
SceneFieldData{SceneField::Mesh, Containers::arrayView(meshObjects), Containers::StridedArrayView1D<const UnsignedInt>{meshesMaterials, &meshesMaterials[0].first(), Containers::arraySize(meshesMaterials), sizeof(meshesMaterials[0])}},
SceneFieldData{SceneField::MeshMaterial, Containers::arrayView(meshObjects), Containers::StridedArrayView1D<const Int>{meshesMaterials, &meshesMaterials[0].second(), Containers::arraySize(meshesMaterials), sizeof(meshesMaterials[0])}},
SceneFieldData{SceneField::Camera, Containers::arrayView(cameraObjects), Containers::arrayView(cameras)},
}));
SceneData scene = Implementation::sceneConvertToSingleFunctionObjects(original, Containers::arrayView({
SceneField::Mesh,
/* Deliberately not including MeshMaterial in the list -- these should
get automatically updated as they share the same object mapping.
OTOH including them would break the output. */
SceneField::Camera,
/* Include also a field that's not present -- it should get skipped */
SceneField::ImporterState
}), 63);
/* There should be three more objects, or the original count preserved if
it's large enough */
CORRADE_COMPARE(scene.objectCount(), data.expectedObjectCount);
/* Object 1 should have a new child that has the camera, as it has a mesh */
CORRADE_COMPARE_AS(scene.childrenFor(1),
Containers::arrayView<UnsignedInt>({66}),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(scene.meshesMaterialsFor(1),
(Containers::arrayView<Containers::Pair<UnsignedInt, Int>>({{7, 2}})),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(scene.camerasFor(1),
Containers::arrayView<UnsignedInt>({}),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(scene.camerasFor(66),
Containers::arrayView<UnsignedInt>({5}),
TestSuite::Compare::Container);
/* Object 15 should have a new child that has the second mesh */
CORRADE_COMPARE_AS(scene.childrenFor(15),
Containers::arrayView<UnsignedInt>({65}),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(scene.meshesMaterialsFor(15),
(Containers::arrayView<Containers::Pair<UnsignedInt, Int>>({{6, 4}})),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(scene.meshesMaterialsFor(65),
(Containers::arrayView<Containers::Pair<UnsignedInt, Int>>({{3, 1}})),
TestSuite::Compare::Container);
/* Object 23 should have two new children that have the second and third
mesh */
CORRADE_COMPARE_AS(scene.childrenFor(23),
Containers::arrayView<UnsignedInt>({63, 64}),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(scene.meshesMaterialsFor(23),
(Containers::arrayView<Containers::Pair<UnsignedInt, Int>>({{1, 0}})),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(scene.meshesMaterialsFor(63),
(Containers::arrayView<Containers::Pair<UnsignedInt, Int>>({{2, 3}})),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(scene.meshesMaterialsFor(64),
(Containers::arrayView<Containers::Pair<UnsignedInt, Int>>({{4, 2}})),
TestSuite::Compare::Container);
/* To be extra sure, verify the actual data. Parents have a few objects
added, the rest is the same */
CORRADE_COMPARE_AS(scene.objectsAsArray(SceneField::Parent), Containers::arrayView<UnsignedInt>({
15, 21, 22, 23, 1, 63, 64, 65, 66
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(scene.parentsAsArray(), Containers::arrayView<Int>({
-1, -1, 1, 2, -1, 3, 3, 0, 4
}), TestSuite::Compare::Container);
/* Meshes have certain objects reassigned (and materials as well, as they
share the same object mapping view), field data stay the same */
CORRADE_COMPARE_AS(scene.objectsAsArray(SceneField::Mesh), Containers::arrayView<UnsignedInt>({
15, 23, 63, 64, 1, 65, 21
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(scene.objectsAsArray(SceneField::MeshMaterial), Containers::arrayView<UnsignedInt>({
15, 23, 63, 64, 1, 65, 21
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(scene.meshesMaterialsAsArray(),
Containers::arrayView(meshesMaterials),
TestSuite::Compare::Container);
/* Cameras have certain objects reassigned, field data stay the same */
CORRADE_COMPARE_AS(scene.objectsAsArray(SceneField::Camera), Containers::arrayView<UnsignedInt>({
22, 66
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(scene.camerasAsArray(),
Containers::arrayView(cameras),
TestSuite::Compare::Container);
}
}}}}
CORRADE_TEST_MAIN(Magnum::Trade::Test::SceneToolsTest)

Loading…
Cancel
Save