Fix #105409: vertex interpolation corrupts Alembic mesh #105867
|
@ -188,7 +188,8 @@ void BKE_cachefile_reader_open(CacheFile *cache_file,
|
|||
case CACHEFILE_TYPE_ALEMBIC:
|
||||
# ifdef WITH_ALEMBIC
|
||||
/* Open Alembic cache reader. */
|
||||
*reader = CacheReader_open_alembic_object(cache_file->handle, *reader, object, object_path);
|
||||
*reader = CacheReader_open_alembic_object(
|
||||
cache_file->handle, *reader, object, object_path, cache_file->is_sequence);
|
||||
# endif
|
||||
break;
|
||||
case CACHEFILE_TYPE_USD:
|
||||
|
|
|
@ -141,7 +141,8 @@ void ABC_CacheReader_free(struct CacheReader *reader);
|
|||
struct CacheReader *CacheReader_open_alembic_object(struct CacheArchiveHandle *handle,
|
||||
struct CacheReader *reader,
|
||||
struct Object *object,
|
||||
const char *object_path);
|
||||
const char *object_path,
|
||||
bool is_sequence);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
@ -465,6 +465,39 @@ static void read_velocity(const V3fArraySamplePtr &velocities,
|
|||
}
|
||||
}
|
||||
|
||||
static bool samples_have_same_topology(const IPolyMeshSchema::Sample &sample,
|
||||
const IPolyMeshSchema::Sample &ceil_sample)
|
||||
{
|
||||
const P3fArraySamplePtr &positions = sample.getPositions();
|
||||
const Alembic::Abc::Int32ArraySamplePtr &face_indices = sample.getFaceIndices();
|
||||
const Alembic::Abc::Int32ArraySamplePtr &face_counts = sample.getFaceCounts();
|
||||
|
||||
const P3fArraySamplePtr &ceil_positions = ceil_sample.getPositions();
|
||||
const Alembic::Abc::Int32ArraySamplePtr &ceil_face_indices = ceil_sample.getFaceIndices();
|
||||
const Alembic::Abc::Int32ArraySamplePtr &ceil_face_counts = ceil_sample.getFaceCounts();
|
||||
|
||||
/* It the counters are different, we can be sure the topology is different. */
|
||||
const bool different_counters = positions->size() != ceil_positions->size() ||
|
||||
face_counts->size() != ceil_face_counts->size() ||
|
||||
face_indices->size() != ceil_face_indices->size();
|
||||
if (different_counters) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Otherwise, we need to check the connectivity as files from e.g. videogrammetry may have the
|
||||
* same face count, but different connections between faces. */
|
||||
|
||||
brecht marked this conversation as resolved
Outdated
|
||||
if (memcmp(face_counts->get(), ceil_face_counts->get(), face_counts->size() * sizeof(int))) {
|
||||
return false;
|
||||
brecht marked this conversation as resolved
Outdated
Brecht Van Lommel
commented
Could the pointers be compared here as well to speed things up in case they are the same?
Could the pointers be compared here as well to speed things up in case they are the same?
```
if (face_counts->get() != ceil_face_counts->get() &&
memcmp(face_counts->get(), ceil_face_counts->get(), face_counts->size() * sizeof(int))) {
return false;
}
```
Kévin Dietrich
commented
I did try that originally but did not notice any difference with or without. Although, we could argue that not having an explicit check on our side is betting on the implementation of memcmp. I would venture that any serious implementation of memcmp would return 0 if the pointers are the same. I did try that originally but did not notice any difference with or without. Although, we could argue that not having an explicit check on our side is betting on the implementation of memcmp. I would venture that any serious implementation of memcmp would return 0 if the pointers are the same.
Brecht Van Lommel
commented
From this, it doesn't seem like something we should rely on: From this, it doesn't seem like something we should rely on:
https://stackoverflow.com/a/58305477
|
||||
}
|
||||
|
||||
if (memcmp(face_indices->get(), ceil_face_indices->get(), face_indices->size() * sizeof(int))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void read_mesh_sample(const std::string &iobject_full_name,
|
||||
ImportSettings *settings,
|
||||
const IPolyMeshSchema &schema,
|
||||
|
@ -483,7 +516,10 @@ static void read_mesh_sample(const std::string &iobject_full_name,
|
|||
if (config.weight != 0.0f) {
|
||||
Alembic::AbcGeom::IPolyMeshSchema::Sample ceil_sample;
|
||||
schema.get(ceil_sample, Alembic::Abc::ISampleSelector(config.ceil_index));
|
||||
abc_mesh_data.ceil_positions = ceil_sample.getPositions();
|
||||
if (samples_have_same_topology(sample, ceil_sample)) {
|
||||
/* Only set interpolation data if the samples are compatible. */
|
||||
abc_mesh_data.ceil_positions = ceil_sample.getPositions();
|
||||
}
|
||||
}
|
||||
|
||||
if ((settings->read_flag & MOD_MESHSEQ_READ_UV) != 0) {
|
||||
|
@ -668,9 +704,47 @@ bool AbcMeshReader::topology_changed(const Mesh *existing_mesh, const ISampleSel
|
|||
const Alembic::Abc::Int32ArraySamplePtr &face_indices = sample.getFaceIndices();
|
||||
const Alembic::Abc::Int32ArraySamplePtr &face_counts = sample.getFaceCounts();
|
||||
|
||||
return positions->size() != existing_mesh->totvert ||
|
||||
face_counts->size() != existing_mesh->totpoly ||
|
||||
face_indices->size() != existing_mesh->totloop;
|
||||
/* It the counters are different, we can be sure the topology is different. */
|
||||
const bool different_counters = positions->size() != existing_mesh->totvert ||
|
||||
face_counts->size() != existing_mesh->totpoly ||
|
||||
face_indices->size() != existing_mesh->totloop;
|
||||
if (different_counters) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Check first if we indeed have multiple samples, unless we read a file sequence in which case
|
||||
* we need to do a full topology comparison. */
|
||||
if (!m_is_reading_a_file_sequence && (m_schema.getFaceIndicesProperty().getNumSamples() == 1 &&
|
||||
brecht marked this conversation as resolved
Outdated
Brecht Van Lommel
commented
I guess this does not work for a sequence of Alembic files, only for a single file that contains the full animation? Or is that a different code path? I guess this does not work for a sequence of Alembic files, only for a single file that contains the full animation? Or is that a different code path?
Kévin Dietrich
commented
You're right, and there is no different code path for files sequences, so this may break; I did not think of that case. I do have some Alembic file sequences, so I can use those to correct the logic. You're right, and there is no different code path for files sequences, so this may break; I did not think of that case. I do have some Alembic file sequences, so I can use those to correct the logic.
|
||||
m_schema.getFaceCountsProperty().getNumSamples() == 1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Otherwise, we need to check the connectivity as files from e.g. videogrammetry may have the
|
||||
* same face count, but different connections between faces. */
|
||||
uint abc_index = 0;
|
||||
|
||||
const int *mesh_corners = existing_mesh->corner_verts().data();
|
||||
const int *mesh_poly_offsets = existing_mesh->poly_offsets().data();
|
||||
|
||||
for (int i = 0; i < face_counts->size(); i++) {
|
||||
if (mesh_poly_offsets[i] != abc_index) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const int abc_face_size = (*face_counts)[i];
|
||||
/* NOTE: Alembic data is stored in the reverse order. */
|
||||
uint rev_loop_index = abc_index + (abc_face_size - 1);
|
||||
for (int f = 0; f < abc_face_size; f++, abc_index++, rev_loop_index--) {
|
||||
const int mesh_vert = mesh_corners[rev_loop_index];
|
||||
const int abc_vert = (*face_indices)[abc_index];
|
||||
if (mesh_vert != abc_vert) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Mesh *AbcMeshReader::read_mesh(Mesh *existing_mesh,
|
||||
|
|
|
@ -33,6 +33,7 @@ AbcObjectReader::AbcObjectReader(const IObject &object, ImportSettings &settings
|
|||
: m_object(nullptr),
|
||||
m_iobject(object),
|
||||
m_settings(&settings),
|
||||
m_is_reading_a_file_sequence(settings.is_sequence),
|
||||
m_min_time(std::numeric_limits<chrono_t>::max()),
|
||||
m_max_time(std::numeric_limits<chrono_t>::min()),
|
||||
m_refcount(0),
|
||||
|
|
|
@ -77,7 +77,11 @@ class AbcObjectReader {
|
|||
Object *m_object;
|
||||
Alembic::Abc::IObject m_iobject;
|
||||
|
||||
/* XXX - TODO(kevindietrich) : this references stack memory... */
|
||||
ImportSettings *m_settings;
|
||||
/* This is initialised from the ImportSettings above on construction. It will need to be removed
|
||||
* once we fix the stack memory reference situation. */
|
||||
bool m_is_reading_a_file_sequence = false;
|
||||
|
||||
chrono_t m_min_time;
|
||||
chrono_t m_max_time;
|
||||
|
|
|
@ -822,6 +822,7 @@ bool ABC_mesh_topology_changed(CacheReader *reader,
|
|||
return false;
|
||||
}
|
||||
|
||||
std::cerr << __func__ << '\n';
|
||||
ISampleSelector sample_sel = sample_selector_for_time(time);
|
||||
return abc_reader->topology_changed(existing_mesh, sample_sel);
|
||||
}
|
||||
|
@ -847,7 +848,8 @@ void ABC_CacheReader_incref(CacheReader *reader)
|
|||
CacheReader *CacheReader_open_alembic_object(CacheArchiveHandle *handle,
|
||||
CacheReader *reader,
|
||||
Object *object,
|
||||
const char *object_path)
|
||||
const char *object_path,
|
||||
const bool is_sequence)
|
||||
{
|
||||
if (object_path[0] == '\0') {
|
||||
return reader;
|
||||
|
@ -867,6 +869,7 @@ CacheReader *CacheReader_open_alembic_object(CacheArchiveHandle *handle,
|
|||
}
|
||||
|
||||
ImportSettings settings;
|
||||
settings.is_sequence = is_sequence;
|
||||
AbcObjectReader *abc_reader = create_reader(iobject, settings);
|
||||
if (abc_reader == nullptr) {
|
||||
/* This object is not supported */
|
||||
|
|
connexions -> connections