mirror of https://github.com/mosra/magnum.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
265 lines
14 KiB
265 lines
14 KiB
#ifndef Magnum_SceneTools_Implementation_convertToSingleFunctionObjects_h |
|
#define Magnum_SceneTools_Implementation_convertToSingleFunctionObjects_h |
|
/* |
|
This file is part of Magnum. |
|
|
|
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, |
|
2020, 2021, 2022 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 <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/SceneTools/Implementation/combine.h" |
|
#include "Magnum/Trade/SceneData.h" |
|
|
|
namespace Magnum { namespace SceneTools { namespace Implementation { |
|
|
|
inline Containers::Optional<std::size_t> findField(Containers::ArrayView<const Trade::SceneField> fields, Trade::SceneField field) { |
|
for(std::size_t i = 0; i != fields.size(); ++i) |
|
if(fields[i] == field) return i; |
|
return {}; |
|
} |
|
|
|
/* Creates a SceneData copy where each object has at most one of the fields |
|
listed in the passed `fieldsToConvert` 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. 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. |
|
|
|
Fields listed in `fieldsToCopy` are copied from the original object. This |
|
is useful for e.g. skins, to preserve them for the separated meshes. |
|
|
|
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 */ |
|
/** @todo it also needs a better name ("make fields unique"?) */ |
|
inline Trade::SceneData convertToSingleFunctionObjects(const Trade::SceneData& scene, Containers::ArrayView<const Trade::SceneField> fieldsToConvert, Containers::ArrayView<const Trade::SceneField> fieldsToCopy, const UnsignedInt newObjectOffset) { |
|
/** @todo assert for really high object counts (where this cast would fail) */ |
|
Containers::Array<UnsignedInt> objectAttachmentCount{ValueInit, std::size_t(scene.mappingBound())}; |
|
for(const Trade::SceneField field: fieldsToConvert) { |
|
CORRADE_INTERNAL_ASSERT(field != Trade::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.mappingAsArray(*fieldId)) { |
|
CORRADE_INTERNAL_ASSERT(object < objectAttachmentCount.size()); |
|
++objectAttachmentCount[object]; |
|
} |
|
} |
|
|
|
/* entriesToAddToFieldsToCopy[i] specifies how many fields to add for the |
|
fieldsToCopy[i] field */ |
|
Containers::Array<UnsignedInt> fieldsToCopyAdditionCount{ValueInit, fieldsToCopy.size()}; |
|
for(std::size_t i = 0; i != fieldsToCopy.size(); ++i) { |
|
const Trade::SceneField field = fieldsToCopy[i]; |
|
CORRADE_INTERNAL_ASSERT(field != Trade::SceneField::Parent); |
|
CORRADE_INTERNAL_ASSERT(!findField(fieldsToConvert, field)); |
|
|
|
/* Skip fields that are not present */ |
|
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.mappingAsArray(*fieldId)) { |
|
CORRADE_INTERNAL_ASSERT(object < objectAttachmentCount.size()); |
|
if(objectAttachmentCount[object]) |
|
fieldsToCopyAdditionCount[i] += objectAttachmentCount[object] - 1; |
|
} |
|
} |
|
|
|
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(Trade::SceneField::Parent); |
|
Containers::Array<Trade::SceneFieldData> fields{scene.fieldCount()}; |
|
for(std::size_t i = 0; i != scene.fieldCount(); ++i) { |
|
const Trade::SceneFieldData& field = scene.fieldData(i); |
|
|
|
/* If this field is among the fields we want to copy, enlarge it for |
|
the new entries */ |
|
if(Containers::Optional<std::size_t> fieldToCopy = findField(fieldsToCopy, field.name())) { |
|
/** @todo wow this placeholder construction is HIDEOUS */ |
|
fields[i] = Trade::SceneFieldData{field.name(), |
|
field.mappingType(), |
|
Containers::ArrayView<const UnsignedInt>{nullptr, std::size_t(field.size() + fieldsToCopyAdditionCount[*fieldToCopy])}, |
|
field.fieldType(), |
|
Containers::StridedArrayView1D<const void>{ |
|
{nullptr, ~std::size_t{}}, |
|
std::size_t(field.size() + fieldsToCopyAdditionCount[*fieldToCopy]), |
|
std::ptrdiff_t((field.fieldArraySize() ? field.fieldArraySize() : 1)*sceneFieldTypeSize(field.fieldType())) |
|
}, |
|
field.fieldArraySize(), |
|
field.flags() & ~Trade::SceneFieldFlag::ImplicitMapping |
|
}; |
|
|
|
/* If this is a parent, enlarge it for the newly added objects, and if |
|
it was implicit make it ordered */ |
|
} else if(field.name() == Trade::SceneField::Parent) { |
|
/** @todo some nicer constructor for placeholders once this is in |
|
public interest */ |
|
fields[i] = Trade::SceneFieldData{Trade::SceneField::Parent, Containers::ArrayView<const UnsignedInt>{nullptr, std::size_t(field.size() + objectsToAdd)}, Containers::ArrayView<const Int>{nullptr, std::size_t(field.size() + objectsToAdd)}, |
|
/* If the field is ordered, we preserve that. But if it's |
|
implicit, we can't. */ |
|
field.flags() & ~(Trade::SceneFieldFlag::ImplicitMapping & ~Trade::SceneFieldFlag::OrderedMapping) |
|
}; |
|
|
|
/* All other fields are copied as-is, but lose the implicit/ordered |
|
flags */ |
|
/** @todo the flags could get preserved for |
|
- fields that don't share their object mapping with any fields in |
|
the fieldsToConvert list |
|
- fields that don't actually get their object mapping touched |
|
during the process (and then all fields that share object |
|
mapping with them) */ |
|
} else fields[i] = Trade::SceneFieldData{field.name(), field.mappingType(), field.mappingData(), field.fieldType(), field.fieldData(), field.fieldArraySize(), field.flags() & ~Trade::SceneFieldFlag::ImplicitMapping}; |
|
} |
|
|
|
/* Combine the fields into a new SceneData */ |
|
Trade::SceneData out = combine(Trade::SceneMappingType::UnsignedInt, Math::max(scene.mappingBound(), UnsignedLong(newObjectOffset) + objectsToAdd), fields); |
|
|
|
/* Copy existing parent object/field data to a prefix of the output */ |
|
const Containers::StridedArrayView1D<UnsignedInt> outParentMapping = out.mutableMapping<UnsignedInt>(parentFieldId); |
|
const Containers::StridedArrayView1D<Int> outParents = out.mutableField<Int>(parentFieldId); |
|
CORRADE_INTERNAL_ASSERT_OUTPUT(scene.parentsInto(0, outParentMapping, outParents) == scene.fieldSize(parentFieldId)); |
|
|
|
/* Copy existing field-to-copy data to a prefix of the output */ |
|
for(std::size_t i = 0; i != fieldsToCopy.size(); ++i) { |
|
const Trade::SceneField field = fieldsToCopy[i]; |
|
|
|
const Containers::Optional<UnsignedInt> fieldId = scene.findFieldId(field); |
|
if(!fieldId) continue; |
|
|
|
const Containers::StridedArrayView1D<UnsignedInt> outMapping = out.mutableMapping<UnsignedInt>(*fieldId); |
|
const Containers::StridedArrayView2D<char> outField = out.mutableField(*fieldId); |
|
CORRADE_INTERNAL_ASSERT_OUTPUT(scene.mappingInto(*fieldId, 0, outMapping) == scene.fieldSize(*fieldId)); |
|
Utility::copy(scene.field(*fieldId), outField.prefix(scene.fieldSize(*fieldId))); |
|
} |
|
|
|
/* List new objects at the end of the extended parent field */ |
|
const Containers::StridedArrayView1D<UnsignedInt> newParentMapping = outParentMapping.exceptPrefix(scene.fieldSize(parentFieldId)); |
|
const Containers::StridedArrayView1D<Int> newParents = outParents.exceptPrefix(scene.fieldSize(parentFieldId)); |
|
for(std::size_t i = 0; i != newParentMapping.size(); ++i) { |
|
newParentMapping[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.mappingBound()), objectAttachmentCount); |
|
|
|
/* Clear the fieldsToCopyAdditionCount array to reuse it below */ |
|
Utility::copy(Containers::stridedArrayView(zero).broadcasted<0>(fieldsToCopy.size()), fieldsToCopyAdditionCount); |
|
|
|
/* For objects with multiple fields move the extra fields to newly added |
|
children */ |
|
{ |
|
std::size_t newParentIndex = 0; |
|
for(const Trade::SceneField field: fieldsToConvert) { |
|
const Containers::Optional<UnsignedInt> fieldId = scene.findFieldId(field); |
|
if(!fieldId) continue; |
|
|
|
for(UnsignedInt& fieldObject: out.mutableMapping<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]) { |
|
/* Go through all fields to copy and copy each entry that |
|
was assigned to the original object */ |
|
for(std::size_t i = 0; i != fieldsToCopy.size(); ++i) { |
|
const Containers::Optional<UnsignedInt> fieldToCopyId = scene.findFieldId(fieldsToCopy[i]); |
|
if(!fieldToCopyId) continue; |
|
|
|
/* View to copy the data from */ |
|
const Containers::StridedArrayView2D<const char> fieldToCopyDataSrc = scene.field(*fieldToCopyId); |
|
|
|
/* Views to put the mapping to and copy the data to */ |
|
const std::size_t newFieldToCopyOffset = scene.fieldSize(*fieldToCopyId); |
|
const Containers::StridedArrayView1D<UnsignedInt> newFieldToCopyMapping = out.mutableMapping<UnsignedInt>(*fieldToCopyId).exceptPrefix(newFieldToCopyOffset); |
|
const Containers::StridedArrayView2D<char> newFieldToCopy = out.mutableField(*fieldToCopyId).exceptPrefix(newFieldToCopyOffset); |
|
|
|
/* As long as there are entries attached to the |
|
original objects, copy them */ |
|
std::size_t offset = 0; |
|
while(Containers::Optional<std::size_t> found = scene.findFieldObjectOffset(*fieldToCopyId, fieldObject, offset)) { |
|
/* Assgn a new field entry to the new object */ |
|
newFieldToCopyMapping[fieldsToCopyAdditionCount[i]] = newParentMapping[newParentIndex]; |
|
|
|
/* Copy the data from the old entry to it */ |
|
Utility::copy(fieldToCopyDataSrc[*found], newFieldToCopy[fieldsToCopyAdditionCount[i]]); |
|
|
|
++fieldsToCopyAdditionCount[i]; |
|
offset = *found + 1; |
|
} |
|
} |
|
|
|
/* Use the old object as a parent of the new object */ |
|
newParents[newParentIndex] = fieldObject; |
|
/* Assign the field to the new object */ |
|
fieldObject = newParentMapping[newParentIndex]; |
|
/* Move to the next reserved object */ |
|
++newParentIndex; |
|
|
|
/** @todo mark this field as touched here and the restore |
|
the original flags for all fields that didn't have |
|
their object mapping touched */ |
|
|
|
} else ++objectAttachmentCount[fieldObject]; |
|
} |
|
} |
|
|
|
CORRADE_INTERNAL_ASSERT(newParentIndex == objectsToAdd); |
|
} |
|
|
|
return out; |
|
} |
|
|
|
}}} |
|
|
|
#endif
|
|
|