Alembic: support reading per-vertex UV sets

This adds support for importing UV sets which are defined per vertex,
instead of per face corners. Such UV sets can be generated when the
mesh is split according to UV islands, or when there is only one UV
island, in which cases only a single UV value can be stored per
vertex since vertices will never be on a seam.

Reviewed By: sybren

Differential Revision: https://developer.blender.org/D11584
This commit is contained in:
Kévin Dietrich 2021-06-15 02:11:07 +02:00
parent 9fed00341e
commit 3385c04598
Notes: blender-bot 2023-02-14 03:13:26 +01:00
Referenced by issue #101090, Alembic attribute import remapping
Referenced by issue #93906, Alembic not importing UVs (if file incorrectly sets geoScope to kVertexScope - even though actual data implies kFacevaryingScope - which 2.93 handled gracefully)
3 changed files with 75 additions and 21 deletions

View File

@ -44,6 +44,7 @@
* in the write code for the conventions. */
using Alembic::AbcGeom::kFacevaryingScope;
using Alembic::AbcGeom::kVaryingScope;
using Alembic::AbcGeom::kVertexScope;
using Alembic::Abc::C4fArraySample;
@ -292,6 +293,7 @@ void write_custom_data(const OCompoundProperty &prop,
using Alembic::Abc::C3fArraySamplePtr;
using Alembic::Abc::C4fArraySamplePtr;
using Alembic::Abc::PropertyHeader;
using Alembic::Abc::UInt32ArraySamplePtr;
using Alembic::AbcGeom::IC3fGeomParam;
using Alembic::AbcGeom::IC4fGeomParam;
@ -300,21 +302,26 @@ using Alembic::AbcGeom::IV3fGeomParam;
static void read_uvs(const CDStreamConfig &config,
void *data,
const AbcUvScope uv_scope,
const Alembic::AbcGeom::V2fArraySamplePtr &uvs,
const Alembic::AbcGeom::UInt32ArraySamplePtr &indices)
const UInt32ArraySamplePtr &indices)
{
MPoly *mpolys = config.mpoly;
MLoop *mloops = config.mloop;
MLoopUV *mloopuvs = static_cast<MLoopUV *>(data);
unsigned int uv_index, loop_index, rev_loop_index;
BLI_assert(uv_scope != ABC_UV_SCOPE_NONE);
const bool do_uvs_per_loop = (uv_scope == ABC_UV_SCOPE_LOOP);
for (int i = 0; i < config.totpoly; i++) {
MPoly &poly = mpolys[i];
unsigned int rev_loop_offset = poly.loopstart + poly.totloop - 1;
for (int f = 0; f < poly.totloop; f++) {
loop_index = poly.loopstart + f;
rev_loop_index = rev_loop_offset - f;
loop_index = do_uvs_per_loop ? poly.loopstart + f : mloops[rev_loop_index].v;
uv_index = (*indices)[loop_index];
const Imath::V2f &uv = (*uvs)[uv_index];
@ -473,13 +480,17 @@ static void read_custom_data_uvs(const ICompoundProperty &prop,
IV2fGeomParam::Sample sample;
uv_param.getIndexed(sample, iss);
if (uv_param.getScope() != kFacevaryingScope) {
UInt32ArraySamplePtr uvs_indices = sample.getIndices();
const AbcUvScope uv_scope = get_uv_scope(uv_param.getScope(), config, uvs_indices);
if (uv_scope == ABC_UV_SCOPE_NONE) {
return;
}
void *cd_data = config.add_customdata_cb(config.mesh, prop_header.getName().c_str(), CD_MLOOPUV);
read_uvs(config, cd_data, sample.getVals(), sample.getIndices());
read_uvs(config, cd_data, uv_scope, sample.getVals(), uvs_indices);
}
void read_generated_coordinates(const ICompoundProperty &prop,
@ -559,4 +570,28 @@ void read_custom_data(const std::string &iobject_full_name,
}
}
/* UVs can be defined per-loop (one value per vertex per face), or per-vertex (one value per
* vertex). The first case is the most common, as this is the standard way of storing this data
* given that some vertices might be on UV seams and have multiple possible UV coordinates; the
* second case can happen when the mesh is split according to the UV islands, in which case storing
* a single UV value per vertex allows to deduplicate data and thus to reduce the file size since
* vertices are guaranteed to only have a single UV coordinate. */
AbcUvScope get_uv_scope(const Alembic::AbcGeom::GeometryScope scope,
const CDStreamConfig &config,
const Alembic::AbcGeom::UInt32ArraySamplePtr &indices)
{
if (scope == kFacevaryingScope && indices->size() == config.totloop) {
return ABC_UV_SCOPE_LOOP;
}
/* kVaryingScope is sometimes used for vertex scopes as the values vary across the vertices. To
* be sure, one has to check the size of the data against the number of vertices, as it could
* also be a varying attribute across the faces (i.e. one value per face). */
if ((scope == kVaryingScope || scope == kVertexScope) && indices->size() == config.totvert) {
return ABC_UV_SCOPE_VERTEX;
}
return ABC_UV_SCOPE_NONE;
}
} // namespace blender::io::alembic

View File

@ -122,4 +122,14 @@ void read_custom_data(const std::string &iobject_full_name,
const CDStreamConfig &config,
const Alembic::Abc::ISampleSelector &iss);
typedef enum {
ABC_UV_SCOPE_NONE,
ABC_UV_SCOPE_LOOP,
ABC_UV_SCOPE_VERTEX,
} AbcUvScope;
AbcUvScope get_uv_scope(const Alembic::AbcGeom::GeometryScope scope,
const CDStreamConfig &config,
const Alembic::AbcGeom::UInt32ArraySamplePtr &indices);
} // namespace blender::io::alembic

View File

@ -121,6 +121,7 @@ struct AbcMeshData {
P3fArraySamplePtr positions;
P3fArraySamplePtr ceil_positions;
AbcUvScope uv_scope;
V2fArraySamplePtr uvs;
UInt32ArraySamplePtr uvs_indices;
};
@ -192,8 +193,9 @@ static void read_mpolys(CDStreamConfig &config, const AbcMeshData &mesh_data)
const UInt32ArraySamplePtr &uvs_indices = mesh_data.uvs_indices;
const bool do_uvs = (mloopuvs && uvs && uvs_indices) &&
(uvs_indices->size() == face_indices->size());
const bool do_uvs = (mloopuvs && uvs && uvs_indices);
const bool do_uvs_per_loop = do_uvs && mesh_data.uv_scope == ABC_UV_SCOPE_LOOP;
BLI_assert(!do_uvs || mesh_data.uv_scope != ABC_UV_SCOPE_NONE);
unsigned int loop_index = 0;
unsigned int rev_loop_index = 0;
unsigned int uv_index = 0;
@ -227,8 +229,7 @@ static void read_mpolys(CDStreamConfig &config, const AbcMeshData &mesh_data)
if (do_uvs) {
MLoopUV &loopuv = mloopuvs[rev_loop_index];
uv_index = (*uvs_indices)[loop_index];
uv_index = (*uvs_indices)[do_uvs_per_loop ? loop_index : loop.v];
/* Some Alembic files are broken (or at least export UVs in a way we don't expect). */
if (uv_index >= uvs_size) {
@ -357,22 +358,29 @@ BLI_INLINE void read_uvs_params(CDStreamConfig &config,
IV2fGeomParam::Sample uvsamp;
uv.getIndexed(uvsamp, selector);
abc_data.uvs = uvsamp.getVals();
abc_data.uvs_indices = uvsamp.getIndices();
UInt32ArraySamplePtr uvs_indices = uvsamp.getIndices();
if (abc_data.uvs_indices->size() == config.totloop) {
std::string name = Alembic::Abc::GetSourceName(uv.getMetaData());
const AbcUvScope uv_scope = get_uv_scope(uv.getScope(), config, uvs_indices);
/* According to the convention, primary UVs should have had their name
* set using Alembic::Abc::SetSourceName, but you can't expect everyone
* to follow it! :) */
if (name.empty()) {
name = uv.getName();
}
void *cd_ptr = config.add_customdata_cb(config.mesh, name.c_str(), CD_MLOOPUV);
config.mloopuv = static_cast<MLoopUV *>(cd_ptr);
if (uv_scope == ABC_UV_SCOPE_NONE) {
return;
}
abc_data.uv_scope = uv_scope;
abc_data.uvs = uvsamp.getVals();
abc_data.uvs_indices = uvs_indices;
std::string name = Alembic::Abc::GetSourceName(uv.getMetaData());
/* According to the convention, primary UVs should have had their name
* set using Alembic::Abc::SetSourceName, but you can't expect everyone
* to follow it! :) */
if (name.empty()) {
name = uv.getName();
}
void *cd_ptr = config.add_customdata_cb(config.mesh, name.c_str(), CD_MLOOPUV);
config.mloopuv = static_cast<MLoopUV *>(cd_ptr);
}
static void *add_customdata_cb(Mesh *mesh, const char *name, int data_type)
@ -462,6 +470,7 @@ CDStreamConfig get_config(Mesh *mesh, const bool use_vertex_interpolation)
config.mvert = mesh->mvert;
config.mloop = mesh->mloop;
config.mpoly = mesh->mpoly;
config.totvert = mesh->totvert;
config.totloop = mesh->totloop;
config.totpoly = mesh->totpoly;
config.loopdata = &mesh->ldata;