Skip to content

Commit

Permalink
StanfordImporter: support custom vertex attributes.
Browse files Browse the repository at this point in the history
  • Loading branch information
mosra committed Mar 11, 2020
1 parent 1d5784a commit dc41fe8
Show file tree
Hide file tree
Showing 11 changed files with 242 additions and 34 deletions.
1 change: 1 addition & 0 deletions doc/changelog-plugins.dox
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ namespace Magnum {
@subsection changelog-plugins-latest-new New features

- Vertex color import in @ref Trade::StanfordImporter "StanfordImporter"
- Custom vertex attribute import in @ref Trade::StanfordImporter "StanfordImporter"
- Support for the `KHR_lights_punctual` extension in
@ref Trade::TinyGltfImporter "TinyGltfImporter", replacing the obsolete
unuspported `KHR_lights_cmn` (see [mosra/magnum-plugins#77](https://github.com/mosra/magnum-plugins/pull/77))
Expand Down
55 changes: 42 additions & 13 deletions src/MagnumPlugins/StanfordImporter/StanfordImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

#include "StanfordImporter.h"

#include <unordered_map>
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/GrowableArray.h>
#include <Corrade/Containers/Optional.h>
Expand All @@ -46,6 +47,9 @@ struct StanfordImporter::State {
UnsignedInt vertexStride{}, vertexCount{}, faceIndicesOffset{}, faceSkip{}, faceCount{};
MeshIndexType faceSizeType{}, faceIndexType{};
bool fileFormatNeedsEndianSwapping;

std::unordered_map<std::string, MeshAttribute> attributeNameMap;
Containers::Array<std::string> attributeNames;
};

StanfordImporter::StanfordImporter() {
Expand Down Expand Up @@ -305,7 +309,18 @@ void StanfordImporter::doOpenData(Containers::ArrayView<const char> data) {
} else if(tokens[2] == "alpha") {
colorOffsets.w() = vertexComponentOffset;
colorFormats.w() = componentFormat;
} else Debug{} << "Trade::StanfordImporter::openData(): ignoring unknown vertex component" << tokens[2];

/* Unknown component, add to the attribute list. Stride is
not known yet, using 0 until it's updated later. */
} else {
auto inserted = state->attributeNameMap.emplace(tokens[2],
meshAttributeCustom(state->attributeNames.size()));
arrayAppend(state->attributeNames, tokens[2]);
arrayAppend(state->attributeData, MeshAttributeData{
inserted.first->second,
componentFormat,
vertexComponentOffset, state->vertexCount, 0});
}

/* Add size of current component to total offset */
vertexComponentOffset += vertexFormatSize(componentFormat);
Expand Down Expand Up @@ -376,11 +391,12 @@ void StanfordImporter::doOpenData(Containers::ArrayView<const char> data) {
return;
}

/* Gather all attributes */
std::size_t attributeCount = 1;
if((colorOffsets < Vector4ui{~UnsignedInt{}}).any()) ++attributeCount;
state->attributeData = Containers::Array<MeshAttributeData>{attributeCount};
std::size_t attributeOffset = 0;
/* Stride is known now, update it in custom attributes */
for(MeshAttributeData& attribute: state->attributeData) {
attribute = MeshAttributeData{
attribute.name(), attribute.format(),
attribute.offset({}), state->vertexCount, std::ptrdiff_t(state->vertexStride)};
}

/* Wrap up positions */
{
Expand All @@ -400,10 +416,10 @@ void StanfordImporter::doOpenData(Containers::ArrayView<const char> data) {
}

/* Add the attribute */
state->attributeData[attributeOffset++] = MeshAttributeData{
arrayAppend(state->attributeData, Containers::InPlaceInit,
MeshAttribute::Position,
vertexFormat(positionFormats.x(), 3, false),
positionOffsets.x(), state->vertexCount, state->vertexStride};
positionOffsets.x(), state->vertexCount, std::ptrdiff_t(state->vertexStride));
}

/* Wrap up colors, if any */
Expand All @@ -427,11 +443,11 @@ void StanfordImporter::doOpenData(Containers::ArrayView<const char> data) {
}

/* Add the attribute */
state->attributeData[attributeOffset++] = MeshAttributeData{
arrayAppend(state->attributeData, Containers::InPlaceInit,
MeshAttribute::Color,
/* We want integer types normalized, 3 or 4 components */
vertexFormat(colorFormats.x(), colorFormats.w() == VertexFormat{} ? 3 : 4, colorFormats.x() != VertexFormat::Float),
colorOffsets.x(), state->vertexCount, state->vertexStride};
colorOffsets.x(), state->vertexCount, std::ptrdiff_t(state->vertexStride));
}

if(data.size() < state->vertexStride*state->vertexCount) {
Expand Down Expand Up @@ -544,7 +560,10 @@ Containers::Optional<MeshData> StanfordImporter::doMesh(UnsignedInt, UnsignedInt
} else if(formatSize == 4) {
for(Containers::StridedArrayView1D<UnsignedInt> component: Containers::arrayCast<2, UnsignedInt>(mutableData, componentCount).transposed<0, 1>())
Utility::Endianness::swapInPlace(component);
} else CORRADE_ASSERT_UNREACHABLE(); /* 8-byte types not supported */
} else if(formatSize == 8) {
for(Containers::StridedArrayView1D<UnsignedLong> component: Containers::arrayCast<2, UnsignedLong>(mutableData, componentCount).transposed<0, 1>())
Utility::Endianness::swapInPlace(component);
} else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
}

if(faceIndexTypeSize == 2)
Expand All @@ -554,8 +573,9 @@ Containers::Optional<MeshData> StanfordImporter::doMesh(UnsignedInt, UnsignedInt
else CORRADE_INTERNAL_ASSERT(faceIndexTypeSize == 1);
}

/* We need to copy the attribute data, so use that opportunity to turn them
from offset-only to absolute */
/* We need to copy the attribute data (also because they use a forbidden
deleter), so use that opportunity to turn them from offset-only to
absolute */
Containers::Array<MeshAttributeData> attributeData{_state->attributeData.size()};
for(std::size_t i = 0; i != attributeData.size(); ++i) {
attributeData[i] = MeshAttributeData{
Expand All @@ -570,6 +590,15 @@ Containers::Optional<MeshData> StanfordImporter::doMesh(UnsignedInt, UnsignedInt
std::move(vertexData), std::move(attributeData)};
}

std::string StanfordImporter::doMeshAttributeName(UnsignedShort name) {
return _state && name < _state->attributeNames.size() ?
_state->attributeNames[name] : "";
}

MeshAttribute StanfordImporter::doMeshAttributeForName(const std::string& name) {
return _state ? _state->attributeNameMap[name] : MeshAttribute{};
}

}}

CORRADE_PLUGIN_REGISTER(StanfordImporter, Magnum::Trade::StanfordImporter,
Expand Down
12 changes: 11 additions & 1 deletion src/MagnumPlugins/StanfordImporter/StanfordImporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ target_link_libraries(your-app PRIVATE MagnumPlugins::StanfordImporter)
See @ref building-plugins, @ref cmake-plugins and @ref plugins for more
information.
@section Trade-StanfordImporter-limitations Behavior and limitations
@section Trade-StanfordImporter-behavior Behavior and limitations
In order to optimize for fast import, the importer supports a restricted subset
of PLY features, which however shouldn't affect any real-world models.
Expand Down Expand Up @@ -132,6 +132,14 @@ using either @ref MeshTools::generateFlatNormals() /
@ref MeshTools::CompileFlag::GenerateFlatNormals /
@ref MeshTools::CompileFlag::GenerateSmoothNormals to @ref MeshTools::compile().
@subsection Trade-StanfordImporter-behavior-custom-attributes Custom attributes
Custom and unrecognized vertex attributes of known types are present in the
imported mesh as well. Their mapping to/from a string can be queried using
@ref meshAttributeName() and @ref meshAttributeForName(). Attributes with
unknown types cause the import to fail, as the format relies on knowing the
type size.
@section Trade-StanfordImporter-configuration Plugin-specific config
It's possible to tune various import options through @ref configuration(). See
Expand Down Expand Up @@ -159,6 +167,8 @@ class MAGNUM_STANFORDIMPORTER_EXPORT StanfordImporter: public AbstractImporter {

MAGNUM_STANFORDIMPORTER_LOCAL UnsignedInt doMeshCount() const override;
MAGNUM_STANFORDIMPORTER_LOCAL Containers::Optional<MeshData> doMesh(UnsignedInt id, UnsignedInt level) override;
MAGNUM_STANFORDIMPORTER_LOCAL MeshAttribute doMeshAttributeForName(const std::string& name) override;
MAGNUM_STANFORDIMPORTER_LOCAL std::string doMeshAttributeName(UnsignedShort name) override;

struct State;
Containers::Pointer<State> _state;
Expand Down
4 changes: 3 additions & 1 deletion src/MagnumPlugins/StanfordImporter/Test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,16 @@ corrade_add_test(StanfordImporterTest StanfordImporterTest.cpp
colors-unsupported-type.ply
colors4-not-same-type.ply
colors4-not-tightly-packed.ply
custom-vertex-components.ply
custom-vertex-components-be.ply
custom-vertex-components-duplicate.ply
crlf.ply
empty.ply
format-invalid.ply
format-missing.ply
format-too-late.ply
format-unsupported.ply
ignored-face-components.ply
ignored-vertex-components.ply
incomplete-face-specification.ply
incomplete-vertex-specification.ply
invalid-face-index-type.ply
Expand Down
143 changes: 131 additions & 12 deletions src/MagnumPlugins/StanfordImporter/Test/StanfordImporterTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ struct StanfordImporterTest: TestSuite::Tester {
void parse();
void empty();

void customAttributes();
void customAttributesDuplicate();
void customAttributesNoFileOpened();

void triangleFastPath();

void openTwice();
Expand Down Expand Up @@ -114,35 +118,42 @@ constexpr struct {
const char* filename;
MeshIndexType indexType;
VertexFormat positionFormat, colorFormat;
UnsignedInt attributeCount;
} ParseData[]{
/* GCC 4.8 doesn't like just {}, needs VertexFormat{} */
{"positions-float-indices-uint", MeshIndexType::UnsignedInt,
VertexFormat::Vector3, VertexFormat{}},
VertexFormat::Vector3, VertexFormat{}, 1},
{"positions-colors-float-indices-int", MeshIndexType::UnsignedInt,
VertexFormat::Vector3, VertexFormat::Vector3},
VertexFormat::Vector3, VertexFormat::Vector3, 2},
/* Four-component colors */
{"positions-colors4-float-indices-int", MeshIndexType::UnsignedInt,
VertexFormat::Vector3, VertexFormat::Vector4},
VertexFormat::Vector3, VertexFormat::Vector4, 2},
/* Testing endian flip */
{"positions-colors-float-indices-int-be", MeshIndexType::UnsignedInt,
VertexFormat::Vector3, VertexFormat::Vector3},
VertexFormat::Vector3, VertexFormat::Vector3, 2},
/* Testing endian flip of unaligned data */
{"positions-colors4-float-indices-int-be-unaligned", MeshIndexType::UnsignedInt,
VertexFormat::Vector3, VertexFormat::Vector4},
VertexFormat::Vector3, VertexFormat::Vector4, 4},
/* Testing various packing variants (hopefully exhausting all combinations) */
{"positions-uchar-indices-ushort", MeshIndexType::UnsignedShort,
VertexFormat::Vector3ub, VertexFormat{}},
VertexFormat::Vector3ub, VertexFormat{}, 1},
{"positions-char-colors4-ushort-indices-short-be", MeshIndexType::UnsignedShort,
VertexFormat::Vector3b, VertexFormat::Vector4usNormalized},
VertexFormat::Vector3b, VertexFormat::Vector4usNormalized, 2},
{"positions-ushort-indices-uchar-be", MeshIndexType::UnsignedByte,
VertexFormat::Vector3us, VertexFormat{}},
VertexFormat::Vector3us, VertexFormat{}, 1},
{"positions-short-colors-uchar-indices-char", MeshIndexType::UnsignedByte,
VertexFormat::Vector3s, VertexFormat::Vector3ubNormalized},
VertexFormat::Vector3s, VertexFormat::Vector3ubNormalized, 2},
/* CR/LF instead of LF */
{"crlf", MeshIndexType::UnsignedByte, VertexFormat::Vector3us, VertexFormat{}},
{"crlf", MeshIndexType::UnsignedByte, VertexFormat::Vector3us, VertexFormat{}, 1},
/* Ignoring extra components */
{"ignored-face-components", MeshIndexType::UnsignedByte, VertexFormat::Vector3b, VertexFormat{}},
{"ignored-vertex-components", MeshIndexType::UnsignedByte, VertexFormat::Vector3us, VertexFormat{}}
{"ignored-face-components", MeshIndexType::UnsignedByte, VertexFormat::Vector3b, VertexFormat{}, 1}
};

constexpr struct {
const char* filename;
} CustomAttributeData[]{
{"custom-vertex-components"},
{"custom-vertex-components-be"}
};

constexpr struct {
Expand All @@ -167,6 +178,12 @@ StanfordImporterTest::StanfordImporterTest() {

addTests({&StanfordImporterTest::empty});

addInstancedTests({&StanfordImporterTest::customAttributes},
Containers::arraySize(CustomAttributeData));

addTests({&StanfordImporterTest::customAttributesDuplicate,
&StanfordImporterTest::customAttributesNoFileOpened});

addInstancedTests({&StanfordImporterTest::triangleFastPath},
Containers::arraySize(FastTrianglePathData));

Expand Down Expand Up @@ -267,6 +284,8 @@ void StanfordImporterTest::parse() {

auto mesh = importer->mesh(0);
CORRADE_VERIFY(mesh);
CORRADE_COMPARE(mesh->attributeCount(), data.attributeCount);

CORRADE_VERIFY(mesh->isIndexed());
CORRADE_COMPARE(mesh->indexType(), data.indexType);
CORRADE_COMPARE_AS(mesh->indicesAsArray(),
Expand Down Expand Up @@ -315,6 +334,106 @@ void StanfordImporterTest::empty() {
CORRADE_COMPARE(mesh->vertexCount(), 0);
}

void StanfordImporterTest::customAttributes() {
auto&& data = CustomAttributeData[testCaseInstanceId()];
setTestCaseDescription(Utility::String::replaceAll(data.filename, "-", " "));

Containers::Pointer<AbstractImporter> importer = _manager.instantiate("StanfordImporter");
CORRADE_VERIFY(importer->openFile(Utility::Directory::join(STANFORDIMPORTER_TEST_DIR, Utility::formatString("{}.ply", data.filename))));

/* The should be available even before the mesh is imported */
const MeshAttribute indexAttribute = importer->meshAttributeForName("index");
CORRADE_COMPARE(indexAttribute, meshAttributeCustom(0));
CORRADE_COMPARE(importer->meshAttributeName(indexAttribute), "index");

const MeshAttribute weightAttribute = importer->meshAttributeForName("weight");
CORRADE_COMPARE(weightAttribute, meshAttributeCustom(1));
CORRADE_COMPARE(importer->meshAttributeName(weightAttribute), "weight");

/* Asking for attribute name that's out of bounds should be handled
gracefully */
CORRADE_COMPARE(importer->meshAttributeName(meshAttributeCustom(1000)), "");

auto mesh = importer->mesh(0);
CORRADE_VERIFY(mesh);
CORRADE_COMPARE(mesh->attributeCount(), 3);
CORRADE_VERIFY(mesh->isIndexed());
CORRADE_COMPARE(mesh->indexType(), MeshIndexType::UnsignedByte);
CORRADE_COMPARE_AS(mesh->indicesAsArray(),
Containers::arrayView(Indices),
TestSuite::Compare::Container);

CORRADE_VERIFY(mesh->hasAttribute(MeshAttribute::Position));
CORRADE_COMPARE(mesh->attributeFormat(MeshAttribute::Position), VertexFormat::Vector3us);
CORRADE_COMPARE_AS(mesh->positions3DAsArray(),
Containers::arrayView(Positions),
TestSuite::Compare::Container);

/* Custom attributes */
CORRADE_VERIFY(mesh->hasAttribute(indexAttribute));
CORRADE_COMPARE(mesh->attributeFormat(indexAttribute), VertexFormat::UnsignedByte);
CORRADE_COMPARE_AS(mesh->attribute<UnsignedByte>(indexAttribute),
Containers::arrayView<UnsignedByte>({
0xaa, 0xab, 0xac, 0xad, 0xae
}), TestSuite::Compare::Container);
CORRADE_VERIFY(mesh->hasAttribute(weightAttribute));
CORRADE_COMPARE(mesh->attributeFormat(weightAttribute), VertexFormat::Double);
CORRADE_COMPARE_AS(mesh->attribute<Double>(weightAttribute),
Containers::arrayView<Double>({
1.23456, 12.3456, 123.456, 1234.56, 12345.6
}), TestSuite::Compare::Container);
}

void StanfordImporterTest::customAttributesDuplicate() {
Containers::Pointer<AbstractImporter> importer = _manager.instantiate("StanfordImporter");
CORRADE_VERIFY(importer->openFile(Utility::Directory::join(STANFORDIMPORTER_TEST_DIR, "custom-vertex-components-duplicate.ply")));

const MeshAttribute weightAttribute = importer->meshAttributeForName("weight");
CORRADE_COMPARE(weightAttribute, meshAttributeCustom(0));
CORRADE_COMPARE(importer->meshAttributeName(weightAttribute), "weight");

auto mesh = importer->mesh(0);
CORRADE_VERIFY(mesh);
CORRADE_COMPARE(mesh->attributeCount(), 4);
CORRADE_VERIFY(mesh->isIndexed());
CORRADE_COMPARE(mesh->indexType(), MeshIndexType::UnsignedByte);
CORRADE_COMPARE_AS(mesh->indicesAsArray(),
Containers::arrayView(Indices),
TestSuite::Compare::Container);

CORRADE_VERIFY(mesh->hasAttribute(MeshAttribute::Position));
CORRADE_COMPARE(mesh->attributeFormat(MeshAttribute::Position), VertexFormat::Vector3us);
CORRADE_COMPARE_AS(mesh->positions3DAsArray(),
Containers::arrayView(Positions),
TestSuite::Compare::Container);

/* Custom attributes. The weight is there three times, it should always be
the same ID, but can be a different format. */
CORRADE_COMPARE(mesh->attributeCount(weightAttribute), 3);
CORRADE_COMPARE(mesh->attributeFormat(weightAttribute, 0), VertexFormat::Float);
CORRADE_COMPARE_AS(mesh->attribute<Float>(weightAttribute, 0),
Containers::arrayView<Float>({
0.1f, 0.2f, 0.3f, 0.4f, 0.5f
}), TestSuite::Compare::Container);
CORRADE_COMPARE(mesh->attributeFormat(weightAttribute, 1), VertexFormat::Float);
CORRADE_COMPARE_AS(mesh->attribute<Float>(weightAttribute, 1),
Containers::arrayView<Float>({
0.7f, 0.1f, 0.3f, 0.6f, 0.4f
}), TestSuite::Compare::Container);
CORRADE_COMPARE(mesh->attributeFormat(weightAttribute, 2), VertexFormat::Double);
CORRADE_COMPARE_AS(mesh->attribute<Double>(weightAttribute, 2),
Containers::arrayView<Double>({
0.2, 0.7, 0.4, 0.0, 0.1
}), TestSuite::Compare::Container);
}

void StanfordImporterTest::customAttributesNoFileOpened() {
Containers::Pointer<AbstractImporter> importer = _manager.instantiate("StanfordImporter");

CORRADE_COMPARE(importer->meshAttributeName(meshAttributeCustom(564)), "");
CORRADE_COMPARE(importer->meshAttributeForName("thing"), MeshAttribute{});
}

void StanfordImporterTest::triangleFastPath() {
auto&& data = FastTrianglePathData[testCaseInstanceId()];
setTestCaseDescription(data.name);
Expand Down
Binary file not shown.
Loading

0 comments on commit dc41fe8

Please sign in to comment.