Skip to content

Commit

Permalink
GltfImporter: use the builtin MeshAttribute::JointIds and Weights.
Browse files Browse the repository at this point in the history
The old custom "JOINTS" and "WEIGHTS" attributes are kept for backwards
compatibility and can be turned off if desired.

A GltfSceneConverter test relied on these being imported as custom, so
it's patched to have the compatibility disabled. Update to
GltfSceneConverter itself to recognize the builtin attributes will
follow eventually as well.
  • Loading branch information
mosra committed Dec 15, 2022
1 parent 8692676 commit cf9c300
Show file tree
Hide file tree
Showing 6 changed files with 301 additions and 99 deletions.
6 changes: 6 additions & 0 deletions src/MagnumPlugins/GltfImporter/GltfImporter.conf
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ textureCoordinateYFlipInMaterial=false
# this name. Change if your file uses a different identifier.
objectIdAttribute=_OBJECT_ID

# Expose MeshAttribute::JointIds and Weights under custom "JOINTS" and
# "WEIGHTS" aliases for backwards compatibility with code that relies on
# those being present. If Magnum is built with MAGNUM_BUILD_DEPRECATED
# disabled, no aliases are provided and this option is ignored.
compatibilitySkinningAttributes=true

# Provide basic Phong material attributes even for PBR materials in order to
# be compatible with PhongMaterialData workflows from version 2020.06 and
# before. This option will eventually become disabled by default.
Expand Down
81 changes: 62 additions & 19 deletions src/MagnumPlugins/GltfImporter/GltfImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,19 +231,24 @@ struct GltfImporter::Document {
we need them in three different places and on-demand construction would
be too annoying to test. */
std::unordered_map<Containers::StringView, SceneField> sceneFieldsForName;
std::unordered_map<Containers::StringView, MeshAttribute>
meshAttributesForName{
/* Not a builtin MeshAttribute yet, but expected to be used by
people until builtin support is added. Wouldn't strictly need to be
present if the file has no skinning meshes but having them present
in the map always makes the implementation simpler. */
std::unordered_map<Containers::StringView, MeshAttribute> meshAttributesForName{
#ifdef MAGNUM_BUILD_DEPRECATED
/* Added as aliases of MeshAttribute::JointIds and
MeshAttribute::Weights for backwards compatibility with code that
used skinning even before there was builtin support for it.
Wouldn't strictly need to be present if the file has no skinning
meshes but having them present in the map always makes the
implementation simpler. */
{"JOINTS"_s, meshAttributeCustom(0)},
{"WEIGHTS"_s, meshAttributeCustom(1)}
#endif
};
Containers::Array<Containers::Pair<Containers::StringView, SceneFieldType>> sceneFieldNamesTypes;
Containers::Array<Containers::StringView> meshAttributeNames{InPlaceInit, {
#ifdef MAGNUM_BUILD_DEPRECATED
"JOINTS"_s,
"WEIGHTS"_s
#endif
}};

/* Mapping for multi-primitive meshes:
Expand Down Expand Up @@ -2998,6 +3003,8 @@ Containers::Optional<MeshData> GltfImporter::doMesh(const UnsignedInt id, Unsign
UnsignedInt bufferId = 0;
UnsignedInt vertexCount = 0;
std::size_t attributeId = 0;
UnsignedInt jointIdAttributeCount = 0;
UnsignedInt weightAttributeCount = 0;
Containers::Pair<Containers::StringView, Int> lastNumberedAttribute;
Math::Range1D<std::size_t> bufferRange;
Containers::Array<MeshAttributeData> attributeData{uniqueAttributeCount};
Expand Down Expand Up @@ -3045,6 +3052,7 @@ Containers::Optional<MeshData> GltfImporter::doMesh(const UnsignedInt id, Unsign
allowed, name stays empty, which produces an error in a single place
below. */
MeshAttribute name{};
UnsignedShort arraySize = 0;
if(baseAttributeName == "POSITION"_s) {
if(accessor->second() == VertexFormat::Vector3 ||
accessor->second() == VertexFormat::Vector3b ||
Expand Down Expand Up @@ -3085,22 +3093,31 @@ Containers::Optional<MeshData> GltfImporter::doMesh(const UnsignedInt id, Unsign
accessor->second() == VertexFormat::Vector3usNormalized ||
accessor->second() == VertexFormat::Vector4usNormalized)
name = MeshAttribute::Color;
/* Not a builtin MeshAttribute yet, but expected to be used by people
until builtin support is added */
/* Joints and weights are represented as an array attribute, so the
vertex format gets changed to a component format and the component
count becomes the array size. */
/** @todo consider merging JOINTS_0, JOINTS_1 etc if they follow each
other and have the same type */
} else if(baseAttributeName == "JOINTS"_s) {
if(accessor->second() == VertexFormat::Vector4ub ||
accessor->second() == VertexFormat::Vector4us)
/** @todo update once these are builtin, but provide an opt-out
compatibility alias */
name = _d->meshAttributesForName.at(baseAttributeName);
accessor->second() == VertexFormat::Vector4us) {
++jointIdAttributeCount;
name = MeshAttribute::JointIds;
arraySize = vertexFormatComponentCount(accessor->second());
accessor->second() = vertexFormatComponentFormat(accessor->second());
}
} else if(baseAttributeName == "WEIGHTS"_s) {
if(accessor->second() == VertexFormat::Vector4 ||
accessor->second() == VertexFormat::Vector4ubNormalized ||
accessor->second() == VertexFormat::Vector4usNormalized)
/** @todo update once these are builtin, but provide an opt-out
compatibility alias */
name = _d->meshAttributesForName.at(baseAttributeName);

accessor->second() == VertexFormat::Vector4usNormalized) {
++weightAttributeCount;
name = MeshAttribute::Weights;
arraySize = vertexFormatComponentCount(accessor->second());
/* vertexFormatComponentFormat() strips the normalized bit from
the format, need to go through the full vertexFormat()
composer instead */
accessor->second() = vertexFormat(accessor->second(), 1, isVertexFormatNormalized(accessor->second()));
}
/* Object ID, name custom. To avoid confusion, print the error together
with saying it's an object ID attribute */
} else if(attribute.first() == configuration().value<Containers::StringView>("objectIdAttribute")) {
Expand Down Expand Up @@ -3170,12 +3187,29 @@ Containers::Optional<MeshData> GltfImporter::doMesh(const UnsignedInt id, Unsign

/* Fill in an attribute. Points to the input data, will be patched to
the output data once we know where it's allocated. */
attributeData[attributeId++] = MeshAttributeData{name, accessor->second(), accessor->first()};
attributeData[attributeId++] = MeshAttributeData{name, accessor->second(), accessor->first(), arraySize};

/* For backwards compatibility insert also a custom "JOINTS" /
"WEIGHTS" attribute which is a Vector4<T> instead of T[4] */
#ifdef MAGNUM_BUILD_DEPRECATED
if((name == MeshAttribute::JointIds || name == MeshAttribute::Weights) && configuration().value<bool>("compatibilitySkinningAttributes")) {
arrayInsert(attributeData, attributeId++, InPlaceInit, _d->meshAttributesForName.at(baseAttributeName), vertexFormat(accessor->second(), arraySize, isVertexFormatNormalized(accessor->second())), accessor->first());
}
#endif
}

/* Verify we really filled all attributes */
CORRADE_INTERNAL_ASSERT(attributeId == attributeData.size());

/* If the deprecated "JOINTS" and "WEIGHTS" attributes were added, the
array was turned into a growable one (for simplicity). Convert it back
to a regular one to avoid assertions in AbstractImporter due to
potentially-dangling deleter pointers */
#ifdef MAGNUM_BUILD_DEPRECATED
if(configuration().value<bool>("compatibilitySkinningAttributes"))
arrayShrink(attributeData, DefaultInit);
#endif

/* 3.7.2.1 (Geometry § Meshes § Overview) says "[count] MUST be non-zero",
but we allow also none unless the strict option is enabled. Not printing
a warning if the strict option is disabled as Magnum can handle the
Expand All @@ -3185,6 +3219,15 @@ Containers::Optional<MeshData> GltfImporter::doMesh(const UnsignedInt id, Unsign
return {};
}

/* 3.7.3.3 (Geometry § Skins § Skinned mesh attributes) says "For a given
primitive, the number of JOINTS_n attribute sets MUST be equal to the
number of WEIGHTS_n attribute sets". Which aligns well with the
assertion that's in MeshData itself. */
if(jointIdAttributeCount != weightAttributeCount) {
Error{} << "Trade::GltfImporter::mesh(): the mesh has" << jointIdAttributeCount << "JOINTS_n attributes but" << weightAttributeCount << "WEIGHTS_n attributes";
return {};
}

/* Allocate & copy vertex data, if any */
Containers::ArrayView<const char> inputVertexData{reinterpret_cast<const char*>(bufferRange.min()), bufferRange.size()};
Containers::Array<char> vertexData{NoInit, bufferRange.size()};
Expand All @@ -3209,7 +3252,7 @@ Containers::Optional<MeshData> GltfImporter::doMesh(const UnsignedInt id, Unsign
vertexCount, attributeData[i].stride()};

attributeData[i] = MeshAttributeData{attributeData[i].name(),
attributeData[i].format(), data};
attributeData[i].format(), data, attributeData[i].arraySize()};

/* Flip Y axis of texture coordinates, unless it's done in the material
instead */
Expand Down
25 changes: 18 additions & 7 deletions src/MagnumPlugins/GltfImporter/GltfImporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -255,13 +255,24 @@ Import of morph data is not supported at the moment.
@ref VertexFormat::Vector4ubNormalized,
@ref VertexFormat::Vector3usNormalized or
@ref VertexFormat::Vector4usNormalized
- Joint IDs and weights for skinning are imported as custom vertex attributes
named "JOINTS" and "WEIGHTS". Their mapping to/from a string can be queried
using @ref meshAttributeName() and @ref meshAttributeForName().
Joint IDs are imported as @ref VertexFormat::Vector4ub or
@ref VertexFormat::Vector4us. Joint weights are imported as
@ref VertexFormat::Vector4, @ref VertexFormat::Vector4ubNormalized or
@ref VertexFormat::Vector4usNormalized.
- Skin joint IDs are imported as *arrays* of @ref VertexFormat::UnsignedByte
or @ref VertexFormat::UnsignedShort, skin weights then as arrays of
@ref VertexFormat::Float, @ref VertexFormat::UnsignedByteNormalized or
@ref VertexFormat::UnsignedShortNormalized. The
@ref MeshData::attributeArraySize() is currently always @cpp 4 @ce, but is
reserved to change (for example representing two consecutive sets as a
single array of 8 items). For backwards compatibility, unless the
@cb{.ini} compatibilitySkinningAttributes @ce
@ref Trade-GltfImporter-configuration "configuration option" or
@ref MAGNUM_BUILD_DEPRECATED is disabled, these are also exposed as custom
@cpp "JOINTS" @ce and @cpp "WEIGHTS" @ce attributes with
@ref VertexFormat::Vector4ub /
@ref VertexFormat::Vector4us and @ref VertexFormat::Vector4 /
@ref VertexFormat::Vector4ubNormalized /
@ref VertexFormat::Vector4usNormalized (non-array) formats, respectively,
with as many instances of these as needed to cover all array items. The
compatibility attributes *alias* the builtin ones, i.e. point to the same
memory, so their presence causes no extra overhead.
- Per-vertex object ID attribute is imported as either
@ref VertexFormat::UnsignedInt, @ref VertexFormat::UnsignedShort or
@ref VertexFormat::UnsignedByte. By default `_OBJECT_ID` is the recognized
Expand Down
Loading

0 comments on commit cf9c300

Please sign in to comment.