Using hasField() + fieldId() was a bad usage pattern leading to a double
linear lookup, so there's now findFieldId() returning an Optional which
covers both. Similarly for finding an object offset in an field, there's
a findFieldObjectOffset() returning an Optional, fieldObjectOffset()
asserting if an object is not found (for convenience to avoid explicit
error handling on user side) and hasFieldObject().
The internal helpers were also renamed and the offset argument moved to
be last for consistency.
To become the central piece of an upcoming SceneTools library, now I
need it just to implement duplication of objects that have more than one
mesh/light/camera/... assignment.
Same as with MeshData2D/3D, the original ObjectData API and plugin
interfaces are preserved to keep existing code as well as existing
importer implementations working. As Magnum's own importers will get
updated to the new SceneData workflow, a backward compatibility layer
provided that translates it to the subset that the legacy ObjectData
understands.
With this commit, both existing plugin code can build (and test against)
the new workflow, and any ports to the new workflow can test against the
legacy interfaces. Except that for now the compatibility layer doesn't
deal with objects that have more than one mesh or for example a light
and a camera attached, this will be done in a separate step.
Support utilities needed for SceneTools that will operate on arbitrary
SceneData (adding/removing fields, objects...) without having to know
what each field means and how it needs to be treated.
Honestly the same would make sense for the VertexFormat enum as well.
But not now, later.
This moves the transformation type consistency checks directly into the
constructor and makes the discovered dimension count available through a
getter. As a result, a lot of duplicate assertions (and
corresponding tests) could be removed while tightening the behavior --
before it was possible to have a 2D transformation matrix complemented
with a 3D rotation quaternion and all asserts would pass, yet the user
would be required to call transformations2DAsArray() *but*
translationsRotationsScalings3DAsArray().
Since scenes can exist without any transformation field, there's both
is2D() and is3D() and they both return false in that case.
For a lack of better API right now, this will be used to check if an
object belongs to a certain scene. In the future, when BitArray is a
thing, there will be a dedicated field with a mask telling which objects
belong to the scene and which not, with a fallback that goes through all
field object mappings and collects objects that have at least one field.
Useful for cases when it's desirable to extract just some elements, for
example just for a particular object ID. Or when allocating an
arbitrarily large array isn't wanted for perf/memory reasons.
These need to query field size for allocating the output array which
means looking up the field by name, but the same lookup was then done
again in the fooInto() implementation.
Reason is that Assimp custom material attribute names are also prefixed
with $ and other weird characters, which could lead to them appearing
before $LayerName, causing a layer to falsely appear unnamed. A space,
instead, is before all printable characters so it's guaranteed to be
always first.
Some things you just don't realize at first. Fortunately the binary
layout isn't pinned yet for the serialization format so this change is
mostly fine.
Looking at the snippets, these seem to have been written back when there
was no builtin shaders yet, it seems, not to mention
MeshTools::compile(), Trade::MeshData or any of the other high-level
APIs. Rather overwhelming to just throw huge code snippets at the user,
explaining a workflow with a custom-made mesh that's going to be drawn
with a custom-made shader, which is like level 999 of using the GL
library.
It was rather discouraging to start "Basic usage" with a boring-ass long
snippet. On the other hand showing just compile() first would lead
people to think it's all some opaque magic, so trying to balance that a
bit.
Also why the hell was the compile() snippet showing the horrendous GL
way of specifying attribute formats? This is not great either but at
least not redundant.
For example if we're looking for a 2D image named "cubemap", the
importer will tell us there's 0 2D images, making a potential mistake
(2D vs 3D) more obvious.
Using openMemory() instead of openData() allows the implementation to
assume the data will stay in scope for as long as needed, which can
prevent unnecessary copies in some plugin implementations.
It warranted a new flag, DataFlag::ExternallyOwned, to describe this
kind of memory. I couldn't reuse Owned as that's used for allocations
owned by the instance, which is too little for certain future use cases.
For example returning *Data instances referencing an Owned memory would
mean the user has to assume the memory is gone when the importer
instance is gone, and that's generally not true for memory passed to
openMemory().
Originally I thought I would do this later, but then realized the
existing plugin implementations would need to get all updated again to
be aware of the new flag, with some being forgotten, and it's just
easier to do the whole thing in a single step.
This makes it much less annoying to pass arbitrarily typed data, such as
std::uint8_t or char8_t and what not. It was already done like this for
the new shader converter plugins, where the input is often 32-bit ints
for SPIR-V.
OTOH the internal virtual API is kept with ArrayView<const char>, as
that makes it easier to operate on by the implementations.
This allows to better describe memory ownership and transfer it instead
of forcing the plugins to allocate their own local copy if the import
happens in-place on the imported data. Right now that's mainly for the
openFile() use case, which implicitly allocated an Array with file
contents only to pass it to openData() which then made a copy because it
could not make any assumption about data scope.
In other words, certain plugins (TgaImporter, KtxImporter, DdsImporter,
CgltfImporter and possibly others) will now have their peak memory usage
*halved*.
Hah, so many overloads. Not providing mutable access to keys or layer
offsets as that would break the invariant of the internal array always
being sorted.