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] */
}
{
/* [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] */
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>
Containers::Array<Containers::Triple<UnsignedInt, Int, MatrixTypeFor<dimensions, Float>>> flattenMeshHierarchyImplementation(const Trade::SceneData& scene, const MatrixTypeFor<dimensions, Float>& globalTransformation) {
template<UnsignedInt dimensions> void flattenMeshHierarchyIntoImplementation(const Trade::SceneData& scene, const Containers::StridedArrayView1D<MatrixTypeFor<dimensions, Float>>& outputTransformations, const MatrixTypeFor<dimensions, Float>& globalTransformation) {
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);
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
that there is a mesh field but it's empty, then for simplicity we still
go through everything. */
if(!scene.hasField(Trade::SceneField::Mesh)) return {};
if(!meshFieldId) return;
/* Allocate a single storage for all temporary data */
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
object mapping, which is subsequently replaced by the absolute object
transformation for given mesh. */
Containers::Array<Containers::Triple<UnsignedInt, Int, MatrixTypeFor<dimensions, Float>>> out{NoInit, scene.fieldSize(Trade::SceneField::Mesh)};
const auto matrices = stridedArrayView(out).slice(&decltype(out)::Type::third);
const auto mapping = Containers::arrayCast<UnsignedInt>(matrices);
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) {
const auto mapping = Containers::arrayCast<UnsignedInt>(outputTransformations);
scene.mappingInto(*meshFieldId, mapping);
for(std::size_t i = 0; i != mapping.size(); ++i) {
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;
}
@ -137,6 +153,14 @@ Containers::Array<Containers::Triple<UnsignedInt, Int, Matrix3>> flattenMeshHier
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) {
return flattenMeshHierarchyImplementation<3>(scene, globalTransformation);
}
@ -145,4 +169,12 @@ Containers::Array<Containers::Triple<UnsignedInt, Int, Matrix4>> flattenMeshHier
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
@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()
*/
#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);
#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
@m_since_latest
@ -101,7 +129,8 @@ an unspecified value.
@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()
*/
#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);
#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

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

@ -44,6 +44,10 @@ struct FlattenMeshHierarchyTest: TestSuite::Tester {
void not2DNot3D();
void noParentField();
void noMeshField();
void into2D();
void into3D();
void intoInvalidSize();
};
using namespace Math::Literals;
@ -70,6 +74,19 @@ const struct {
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() {
addInstancedTests({&FlattenMeshHierarchyTest::test2D,
&FlattenMeshHierarchyTest::test3D},
@ -78,6 +95,12 @@ FlattenMeshHierarchyTest::FlattenMeshHierarchyTest() {
addTests({&FlattenMeshHierarchyTest::not2DNot3D,
&FlattenMeshHierarchyTest::noParentField,
&FlattenMeshHierarchyTest::noMeshField});
addInstancedTests({&FlattenMeshHierarchyTest::into2D,
&FlattenMeshHierarchyTest::into3D},
Containers::arraySize(IntoData));
addTests({&FlattenMeshHierarchyTest::intoInvalidSize});
}
const struct Scene {
@ -317,6 +340,141 @@ void FlattenMeshHierarchyTest::noMeshField() {
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)

Loading…
Cancel
Save