Browse Source

SceneTools: add flattenMeshHierarchyInto().

Now that the data order is pinned down, it's possible to hammer a more
efficient underlying API from out under the convenience utility.
pull/610/head
Vladimír Vondruš 4 years ago
parent
commit
ba8a3e6f3f
  1. 39
      doc/snippets/MagnumSceneTools.cpp
  2. 58
      src/Magnum/SceneTools/FlattenMeshHierarchy.cpp
  3. 60
      src/Magnum/SceneTools/FlattenMeshHierarchy.h
  4. 158
      src/Magnum/SceneTools/Test/FlattenMeshHierarchyTest.cpp

39
doc/snippets/MagnumSceneTools.cpp

@ -71,6 +71,45 @@ for(const Containers::Triple<UnsignedInt, Int, Matrix4>& meshTransformation:
/* [flattenMeshHierarchy3D-transformations] */ /* [flattenMeshHierarchy3D-transformations] */
} }
{
/* [flattenMeshHierarchy2DInto] */
Trade::SceneData scene = DOXYGEN_ELLIPSIS(Trade::SceneData{{}, 0, nullptr, {}});
struct Data {
Matrix3 transformation;
UnsignedInt object;
UnsignedInt mesh;
};
Containers::Array<Data> data{NoInit, scene.fieldSize(Trade::SceneField::Mesh)};
SceneTools::flattenMeshHierarchy2DInto(scene,
stridedArrayView(data).slice(&Data::transformation));
scene.meshesMaterialsInto(
stridedArrayView(data).slice(&Data::object),
stridedArrayView(data).slice(&Data::mesh),
nullptr);
/* [flattenMeshHierarchy2DInto] */
} {
/* [flattenMeshHierarchy3DInto] */
Trade::SceneData scene = DOXYGEN_ELLIPSIS(Trade::SceneData{{}, 0, nullptr, {}});
struct Data {
Matrix4 transformation;
UnsignedInt object;
UnsignedInt mesh;
};
Containers::Array<Data> data{NoInit, scene.fieldSize(Trade::SceneField::Mesh)};
SceneTools::flattenMeshHierarchy3DInto(scene,
stridedArrayView(data).slice(&Data::transformation));
scene.meshesMaterialsInto(
stridedArrayView(data).slice(&Data::object),
stridedArrayView(data).slice(&Data::mesh),
nullptr);
/* [flattenMeshHierarchy3DInto] */
}
{ {
/* [orderClusterParents-transformations] */ /* [orderClusterParents-transformations] */
Trade::SceneData scene = DOXYGEN_ELLIPSIS(Trade::SceneData{{}, 0, nullptr, {}}); Trade::SceneData scene = DOXYGEN_ELLIPSIS(Trade::SceneData{{}, 0, nullptr, {}});

58
src/Magnum/SceneTools/FlattenMeshHierarchy.cpp

@ -60,18 +60,20 @@ template<> struct SceneDataDimensionTraits<3> {
} }
}; };
template<UnsignedInt dimensions> template<UnsignedInt dimensions> void flattenMeshHierarchyIntoImplementation(const Trade::SceneData& scene, const Containers::StridedArrayView1D<MatrixTypeFor<dimensions, Float>>& outputTransformations, const MatrixTypeFor<dimensions, Float>& globalTransformation) {
Containers::Array<Containers::Triple<UnsignedInt, Int, MatrixTypeFor<dimensions, Float>>> flattenMeshHierarchyImplementation(const Trade::SceneData& scene, const MatrixTypeFor<dimensions, Float>& globalTransformation) {
CORRADE_ASSERT(SceneDataDimensionTraits<dimensions>::isDimensions(scene), CORRADE_ASSERT(SceneDataDimensionTraits<dimensions>::isDimensions(scene),
"SceneTools::flattenMeshHierarchy(): the scene is not" << dimensions << Debug::nospace << "D", {}); "SceneTools::flattenMeshHierarchy(): the scene is not" << dimensions << Debug::nospace << "D", );
const Containers::Optional<UnsignedInt> parentFieldId = scene.findFieldId(Trade::SceneField::Parent); const Containers::Optional<UnsignedInt> parentFieldId = scene.findFieldId(Trade::SceneField::Parent);
CORRADE_ASSERT(parentFieldId, CORRADE_ASSERT(parentFieldId,
"SceneTools::flattenMeshHierarchy(): the scene has no hierarchy", {}); "SceneTools::flattenMeshHierarchy(): the scene has no hierarchy", );
Containers::Optional<UnsignedInt> meshFieldId = scene.findFieldId(Trade::SceneField::Mesh);
CORRADE_ASSERT(outputTransformations.size() == (meshFieldId ? scene.fieldSize(*meshFieldId) : 0),
"SceneTools::flattenMeshHierarchyInto(): bad output size, expected" << scene.fieldSize(*meshFieldId) << "but got" << outputTransformations.size(), );
/* If there's no mesh field in the file, nothing to do. Another case is /* If there's no mesh field in the file, nothing to do. Another case is
that there is a mesh field but it's empty, then for simplicity we still that there is a mesh field but it's empty, then for simplicity we still
go through everything. */ go through everything. */
if(!scene.hasField(Trade::SceneField::Mesh)) return {}; if(!meshFieldId) return;
/* Allocate a single storage for all temporary data */ /* Allocate a single storage for all temporary data */
Containers::ArrayView<Containers::Pair<UnsignedInt, Int>> orderedClusteredParents; Containers::ArrayView<Containers::Pair<UnsignedInt, Int>> orderedClusteredParents;
@ -113,16 +115,30 @@ Containers::Array<Containers::Triple<UnsignedInt, Int, MatrixTypeFor<dimensions,
absolute transformations to each. The matrix location is abused for absolute transformations to each. The matrix location is abused for
object mapping, which is subsequently replaced by the absolute object object mapping, which is subsequently replaced by the absolute object
transformation for given mesh. */ transformation for given mesh. */
Containers::Array<Containers::Triple<UnsignedInt, Int, MatrixTypeFor<dimensions, Float>>> out{NoInit, scene.fieldSize(Trade::SceneField::Mesh)}; const auto mapping = Containers::arrayCast<UnsignedInt>(outputTransformations);
const auto matrices = stridedArrayView(out).slice(&decltype(out)::Type::third); scene.mappingInto(*meshFieldId, mapping);
const auto mapping = Containers::arrayCast<UnsignedInt>(matrices); for(std::size_t i = 0; i != mapping.size(); ++i) {
scene.meshesMaterialsInto(mapping,
stridedArrayView(out).slice(&decltype(out)::Type::first),
stridedArrayView(out).slice(&decltype(out)::Type::second));
for(std::size_t i = 0; i != out.size(); ++i) {
CORRADE_INTERNAL_ASSERT(mapping[i] < scene.mappingBound()); CORRADE_INTERNAL_ASSERT(mapping[i] < scene.mappingBound());
matrices[i] = absoluteTransformations[mapping[i] + 1]; outputTransformations[i] = absoluteTransformations[mapping[i] + 1];
} }
}
template<UnsignedInt dimensions> Containers::Array<Containers::Triple<UnsignedInt, Int, MatrixTypeFor<dimensions, Float>>> flattenMeshHierarchyImplementation(const Trade::SceneData& scene, const MatrixTypeFor<dimensions, Float>& globalTransformation) {
const Containers::Optional<UnsignedInt> meshFieldId = scene.findFieldId(Trade::SceneField::Mesh);
/* Get the transformations. This will be a no-op if the mesh field isn't
present, but will go through other assertions that may still be rather
valuable */
Containers::Array<Containers::Triple<UnsignedInt, Int, MatrixTypeFor<dimensions, Float>>> out{NoInit, meshFieldId ? scene.fieldSize(*meshFieldId) : 0};
flattenMeshHierarchyIntoImplementation<dimensions>(scene,
stridedArrayView(out).slice(&decltype(out)::Type::third),
globalTransformation);
/* Fetch the additional mesh and material ID as well, which are in the
same order */
if(meshFieldId) scene.meshesMaterialsInto(nullptr,
stridedArrayView(out).slice(&decltype(out)::Type::first),
stridedArrayView(out).slice(&decltype(out)::Type::second));
return out; return out;
} }
@ -137,6 +153,14 @@ Containers::Array<Containers::Triple<UnsignedInt, Int, Matrix3>> flattenMeshHier
return flattenMeshHierarchyImplementation<2>(scene, {}); return flattenMeshHierarchyImplementation<2>(scene, {});
} }
void flattenMeshHierarchy2DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D<Matrix3>& transformations, const Matrix3& globalTransformation) {
return flattenMeshHierarchyIntoImplementation<2>(scene, transformations, globalTransformation);
}
void flattenMeshHierarchy2DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D<Matrix3>& transformations) {
return flattenMeshHierarchyIntoImplementation<2>(scene, transformations, {});
}
Containers::Array<Containers::Triple<UnsignedInt, Int, Matrix4>> flattenMeshHierarchy3D(const Trade::SceneData& scene, const Matrix4& globalTransformation) { Containers::Array<Containers::Triple<UnsignedInt, Int, Matrix4>> flattenMeshHierarchy3D(const Trade::SceneData& scene, const Matrix4& globalTransformation) {
return flattenMeshHierarchyImplementation<3>(scene, globalTransformation); return flattenMeshHierarchyImplementation<3>(scene, globalTransformation);
} }
@ -145,4 +169,12 @@ Containers::Array<Containers::Triple<UnsignedInt, Int, Matrix4>> flattenMeshHier
return flattenMeshHierarchyImplementation<3>(scene, {}); return flattenMeshHierarchyImplementation<3>(scene, {});
} }
void flattenMeshHierarchy3DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D<Matrix4>& transformations, const Matrix4& globalTransformation) {
return flattenMeshHierarchyIntoImplementation<3>(scene, transformations, globalTransformation);
}
void flattenMeshHierarchy3DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D<Matrix4>& transformations) {
return flattenMeshHierarchyIntoImplementation<3>(scene, transformations, {});
}
}} }}

60
src/Magnum/SceneTools/FlattenMeshHierarchy.h

@ -63,7 +63,8 @@ an unspecified value.
@experimental @experimental
@see @ref Trade::SceneData::hasField(), @ref Trade::SceneData::is2D(), @see @ref flattenMeshHierarchy2DInto(), @ref flattenMeshHierarchy3D(),
@ref Trade::SceneData::hasField(), @ref Trade::SceneData::is2D(),
@ref MeshTools::concatenate() @ref MeshTools::concatenate()
*/ */
#ifdef DOXYGEN_GENERATING_OUTPUT #ifdef DOXYGEN_GENERATING_OUTPUT
@ -74,6 +75,33 @@ MAGNUM_SCENETOOLS_EXPORT Containers::Array<Containers::Triple<UnsignedInt, Int,
MAGNUM_SCENETOOLS_EXPORT Containers::Array<Containers::Triple<UnsignedInt, Int, Matrix3>> flattenMeshHierarchy2D(const Trade::SceneData& scene); MAGNUM_SCENETOOLS_EXPORT Containers::Array<Containers::Triple<UnsignedInt, Int, Matrix3>> flattenMeshHierarchy2D(const Trade::SceneData& scene);
#endif #endif
/**
@brief Flatten a 2D mesh hierarchy into an existing array
@param[in] scene Input scene
@param[out] transformations Where to put the calculated transformations
@param[in] globalTransformation Global transformation to prepend
@m_since_latest
A variant of @ref flattenMeshHierarchy2D() that fills existing memory instead
of allocating a new array. The @p transformations array is expected to have the
same size as the @ref Trade::SceneField::Mesh field. Corresponding mesh and
material IDs as well as object ID mapping can be retrieved directly with
@ref Trade::SceneData::meshesMaterialsInto(), as the returned transformations
are matching their order. The snippet below shows retrieving absolute
transformations together with object and mesh IDs, but ignoring materials:
@snippet MagnumSceneTools.cpp flattenMeshHierarchy2DInto
@experimental
*/
#ifdef DOXYGEN_GENERATING_OUTPUT
MAGNUM_SCENETOOLS_EXPORT void flattenMeshHierarchy2DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D<Matrix3>& transformations, const Matrix3& globalTransformation = {});
#else
/* To avoid including Matrix3 */
MAGNUM_SCENETOOLS_EXPORT void flattenMeshHierarchy2DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D<Matrix3>& transformations, const Matrix3& globalTransformation);
MAGNUM_SCENETOOLS_EXPORT void flattenMeshHierarchy2DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D<Matrix3>& transformations);
#endif
/** /**
@brief Flatten a 3D mesh hierarchy @brief Flatten a 3D mesh hierarchy
@m_since_latest @m_since_latest
@ -101,7 +129,8 @@ an unspecified value.
@experimental @experimental
@see @ref Trade::SceneData::hasField(), @ref Trade::SceneData::is3D(), @see @ref flattenMeshHierarchy3DInto(), @ref flattenMeshHierarchy2D(),
@ref Trade::SceneData::hasField(), @ref Trade::SceneData::is3D(),
@ref MeshTools::concatenate() @ref MeshTools::concatenate()
*/ */
#ifdef DOXYGEN_GENERATING_OUTPUT #ifdef DOXYGEN_GENERATING_OUTPUT
@ -112,6 +141,33 @@ MAGNUM_SCENETOOLS_EXPORT Containers::Array<Containers::Triple<UnsignedInt, Int,
MAGNUM_SCENETOOLS_EXPORT Containers::Array<Containers::Triple<UnsignedInt, Int, Matrix4>> flattenMeshHierarchy3D(const Trade::SceneData& scene); MAGNUM_SCENETOOLS_EXPORT Containers::Array<Containers::Triple<UnsignedInt, Int, Matrix4>> flattenMeshHierarchy3D(const Trade::SceneData& scene);
#endif #endif
/**
@brief Flatten a 3D mesh hierarchy into an existing array
@param[in] scene Input scene
@param[out] transformations Where to put the calculated transformations
@param[in] globalTransformation Global transformation to prepend
@m_since_latest
A variant of @ref flattenMeshHierarchy3D() that fills existing memory instead
of allocating a new array. The @p transformations array is expected to have the
same size as the @ref Trade::SceneField::Mesh field. Corresponding mesh and
material IDs as well as object ID mapping can be retrieved directly with
@ref Trade::SceneData::meshesMaterialsInto(), as the returned transformations
are matching their order. The snippet below shows retrieving absolute
transformations together with object and mesh IDs, but ignoring materials:
@snippet MagnumSceneTools.cpp flattenMeshHierarchy3DInto
@experimental
*/
#ifdef DOXYGEN_GENERATING_OUTPUT
MAGNUM_SCENETOOLS_EXPORT void flattenMeshHierarchy3DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D<Matrix4>& transformations, const Matrix4& globalTransformation = {});
#else
/* To avoid including Matrix3 */
MAGNUM_SCENETOOLS_EXPORT void flattenMeshHierarchy3DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D<Matrix4>& transformations, const Matrix4& globalTransformation);
MAGNUM_SCENETOOLS_EXPORT void flattenMeshHierarchy3DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D<Matrix4>& transformations);
#endif
}} }}
#endif #endif

158
src/Magnum/SceneTools/Test/FlattenMeshHierarchyTest.cpp

@ -44,6 +44,10 @@ struct FlattenMeshHierarchyTest: TestSuite::Tester {
void not2DNot3D(); void not2DNot3D();
void noParentField(); void noParentField();
void noMeshField(); void noMeshField();
void into2D();
void into3D();
void intoInvalidSize();
}; };
using namespace Math::Literals; using namespace Math::Literals;
@ -70,6 +74,19 @@ const struct {
0}, 0},
}; };
const struct {
const char* name;
Matrix3 globalTransformation2D;
Matrix4 globalTransformation3D;
std::size_t expectedOutputSize;
} IntoData[]{
{"", {}, {},
5},
{"global transformation",
Matrix3::scaling(Vector2{0.5f}), Matrix4::scaling(Vector3{0.5f}),
5},
};
FlattenMeshHierarchyTest::FlattenMeshHierarchyTest() { FlattenMeshHierarchyTest::FlattenMeshHierarchyTest() {
addInstancedTests({&FlattenMeshHierarchyTest::test2D, addInstancedTests({&FlattenMeshHierarchyTest::test2D,
&FlattenMeshHierarchyTest::test3D}, &FlattenMeshHierarchyTest::test3D},
@ -78,6 +95,12 @@ FlattenMeshHierarchyTest::FlattenMeshHierarchyTest() {
addTests({&FlattenMeshHierarchyTest::not2DNot3D, addTests({&FlattenMeshHierarchyTest::not2DNot3D,
&FlattenMeshHierarchyTest::noParentField, &FlattenMeshHierarchyTest::noParentField,
&FlattenMeshHierarchyTest::noMeshField}); &FlattenMeshHierarchyTest::noMeshField});
addInstancedTests({&FlattenMeshHierarchyTest::into2D,
&FlattenMeshHierarchyTest::into3D},
Containers::arraySize(IntoData));
addTests({&FlattenMeshHierarchyTest::intoInvalidSize});
} }
const struct Scene { const struct Scene {
@ -317,6 +340,141 @@ void FlattenMeshHierarchyTest::noMeshField() {
TestSuite::Compare::Container); TestSuite::Compare::Container);
} }
void FlattenMeshHierarchyTest::into2D() {
auto&& data = IntoData[testCaseInstanceId()];
setTestCaseDescription(data.name);
/* The *Into() variant is the actual base implementation, so just verify
that the data get correctly propagated through. Everything else is
tested above already. */
Trade::SceneData scene{Trade::SceneMappingType::UnsignedShort, 33, {}, Data, {
Trade::SceneFieldData{Trade::SceneField::Parent,
Containers::stridedArrayView(Data->parents)
.slice(&Scene::Parent::object),
Containers::stridedArrayView(Data->parents)
.slice(&Scene::Parent::parent)},
Trade::SceneFieldData{Trade::SceneField::Transformation,
Containers::stridedArrayView(Data->transforms)
.slice(&Scene::Transformation::object),
Containers::stridedArrayView(Data->transforms)
.slice(&Scene::Transformation::transformation2D)},
Trade::SceneFieldData{Trade::SceneField::Mesh,
Containers::stridedArrayView(Data->meshes)
.slice(&Scene::Mesh::object),
Containers::stridedArrayView(Data->meshes)
.slice(&Scene::Mesh::mesh)}
}};
Containers::Array<Matrix3> out{NoInit, scene.fieldSize(Trade::SceneField::Mesh)};
/* To test the parameter-less overload also */
if(data.globalTransformation2D != Matrix3{})
flattenMeshHierarchy2DInto(scene, out, data.globalTransformation2D);
else
flattenMeshHierarchy2DInto(scene, out);
CORRADE_COMPARE_AS(out, Containers::arrayView<Matrix3>({
data.globalTransformation2D*
Matrix3::translation({1.0f, -1.5f})*
Matrix3::scaling({3.0f, 5.0f}),
data.globalTransformation2D*
Matrix3::translation({1.0f, -1.5f})*
Matrix3::rotation(35.0_degf),
data.globalTransformation2D,
data.globalTransformation2D*
Matrix3::translation({1.0f, -1.5f})*
Matrix3::rotation(35.0_degf),
data.globalTransformation2D*
Matrix3::translation({1.0f, -1.5f})*
Matrix3::scaling({3.0f, 5.0f})
}), TestSuite::Compare::Container);
}
void FlattenMeshHierarchyTest::into3D() {
auto&& data = IntoData[testCaseInstanceId()];
setTestCaseDescription(data.name);
/* The *Into() variant is the actual base implementation, so just verify
that the data get correctly propagated through. Everything else is
tested above already. */
Trade::SceneData scene{Trade::SceneMappingType::UnsignedShort, 33, {}, Data, {
Trade::SceneFieldData{Trade::SceneField::Parent,
Containers::stridedArrayView(Data->parents)
.slice(&Scene::Parent::object),
Containers::stridedArrayView(Data->parents)
.slice(&Scene::Parent::parent)},
Trade::SceneFieldData{Trade::SceneField::Transformation,
Containers::stridedArrayView(Data->transforms)
.slice(&Scene::Transformation::object),
Containers::stridedArrayView(Data->transforms)
.slice(&Scene::Transformation::transformation3D)},
Trade::SceneFieldData{Trade::SceneField::Mesh,
Containers::stridedArrayView(Data->meshes)
.slice(&Scene::Mesh::object),
Containers::stridedArrayView(Data->meshes)
.slice(&Scene::Mesh::mesh)}
}};
Containers::Array<Matrix4> out{NoInit, scene.fieldSize(Trade::SceneField::Mesh)};
/* To test the parameter-less overload also */
if(data.globalTransformation3D != Matrix4{})
flattenMeshHierarchy3DInto(scene, out, data.globalTransformation3D);
else
flattenMeshHierarchy3DInto(scene, out);
CORRADE_COMPARE_AS(out, Containers::arrayView<Matrix4>({
data.globalTransformation3D*
Matrix4::translation({1.0f, -1.5f, 0.5f})*
Matrix4::scaling({3.0f, 5.0f, 2.0f}),
data.globalTransformation3D*
Matrix4::translation({1.0f, -1.5f, 0.5f})*
Matrix4::rotationZ(35.0_degf),
data.globalTransformation3D,
data.globalTransformation3D*
Matrix4::translation({1.0f, -1.5f, 0.5f})*
Matrix4::rotationZ(35.0_degf),
data.globalTransformation3D*
Matrix4::translation({1.0f, -1.5f, 0.5f})*
Matrix4::scaling({3.0f, 5.0f, 2.0f})
}), TestSuite::Compare::Container);
}
void FlattenMeshHierarchyTest::intoInvalidSize() {
CORRADE_SKIP_IF_NO_ASSERT();
struct Data {
UnsignedInt mapping;
UnsignedInt mesh;
} data[5]{};
Trade::SceneData scene2D{Trade::SceneMappingType::UnsignedInt, 1, {}, data, {
Trade::SceneFieldData{Trade::SceneField::Mesh,
Containers::stridedArrayView(data).slice(&Data::mapping),
Containers::stridedArrayView(data).slice(&Data::mesh)},
Trade::SceneFieldData{Trade::SceneField::Parent, Trade::SceneMappingType::UnsignedInt, nullptr, Trade::SceneFieldType::Int, nullptr},
Trade::SceneFieldData{Trade::SceneField::Transformation, Trade::SceneMappingType::UnsignedInt, nullptr, Trade::SceneFieldType::Matrix3x3, nullptr}
}};
Trade::SceneData scene3D{Trade::SceneMappingType::UnsignedInt, 1, {}, data, {
Trade::SceneFieldData{Trade::SceneField::Mesh,
Containers::stridedArrayView(data).slice(&Data::mapping),
Containers::stridedArrayView(data).slice(&Data::mesh)},
Trade::SceneFieldData{Trade::SceneField::Parent, Trade::SceneMappingType::UnsignedInt, nullptr, Trade::SceneFieldType::Int, nullptr},
Trade::SceneFieldData{Trade::SceneField::Transformation, Trade::SceneMappingType::UnsignedInt, nullptr, Trade::SceneFieldType::Matrix4x4, nullptr}
}};
Matrix3 transformations2D[6];
Matrix4 transformations3D[4];
std::ostringstream out;
Error redirectError{&out};
flattenMeshHierarchy2DInto(scene2D, transformations2D);
flattenMeshHierarchy3DInto(scene3D, transformations3D);
CORRADE_COMPARE(out.str(),
"SceneTools::flattenMeshHierarchyInto(): bad output size, expected 5 but got 6\n"
"SceneTools::flattenMeshHierarchyInto(): bad output size, expected 5 but got 4\n");
}
}}}} }}}}
CORRADE_TEST_MAIN(Magnum::SceneTools::Test::FlattenMeshHierarchyTest) CORRADE_TEST_MAIN(Magnum::SceneTools::Test::FlattenMeshHierarchyTest)

Loading…
Cancel
Save