Fix T51534: Alembic: added support for face-varying vertex colours
authorSybren A. Stüvel <sybren@stuvel.eu>
Tue, 23 May 2017 15:27:09 +0000 (17:27 +0200)
committerSybren A. Stüvel <sybren@stuvel.eu>
Tue, 23 May 2017 15:27:15 +0000 (17:27 +0200)
Houdini writes vertex data in a different format than Blender does; Houdini
uses "face-varying scope", which means that the vertex colours are indexed
by an ever-increasing number over all vertices of all faces instead of the
vertex index.

I've also merged the read_custom_data_mcols() and read_mcols() functions,
because the latter was only called from the former, and the changes in this
commit would add yet more function parameters to pass.

source/blender/alembic/intern/abc_customdata.cc
tests/python/bl_alembic_import_test.py

index c42e5f808b5fbfe1dc2bce8b6e83ae2f49f0be1c..1d2bc6890271f0bc1f368a79bd9763a441442a2f 100644 (file)
@@ -227,44 +227,6 @@ using Alembic::AbcGeom::IC3fGeomParam;
 using Alembic::AbcGeom::IC4fGeomParam;
 using Alembic::AbcGeom::IV2fGeomParam;
 
-static void read_mcols(const CDStreamConfig &config, void *data,
-                       const C3fArraySamplePtr &c3f_ptr,
-                       const C4fArraySamplePtr &c4f_ptr)
-{
-       MCol *cfaces = static_cast<MCol *>(data);
-       MPoly *polys = config.mpoly;
-       MLoop *mloops = config.mloop;
-
-       /* Either one or the other should be given. */
-       BLI_assert(c3f_ptr || c4f_ptr);
-       const bool use_c3f_ptr = (c3f_ptr.get() != nullptr);
-
-       for (int i = 0; i < config.totpoly; ++i) {
-               MPoly *p = &polys[i];
-               MCol *cface = &cfaces[p->loopstart + p->totloop];
-               MLoop *mloop = &mloops[p->loopstart + p->totloop];
-
-               for (int j = 0; j < p->totloop; ++j) {
-                       cface--;
-                       mloop--;
-
-                       if (use_c3f_ptr) {
-                               const Imath::C3f &color = (*c3f_ptr)[mloop->v];
-                               cface->a = FTOCHAR(color[0]);
-                               cface->r = FTOCHAR(color[1]);
-                               cface->g = FTOCHAR(color[2]);
-                               cface->b = 255;
-                       }
-                       else {
-                               const Imath::C4f &color = (*c4f_ptr)[mloop->v];
-                               cface->a = FTOCHAR(color[0]);
-                               cface->r = FTOCHAR(color[1]);
-                               cface->g = FTOCHAR(color[2]);
-                               cface->b = FTOCHAR(color[3]);
-                       }
-               }
-       }
-}
 
 static void read_uvs(const CDStreamConfig &config, void *data,
                      const Alembic::AbcGeom::V2fArraySamplePtr &uvs,
@@ -290,34 +252,83 @@ static void read_uvs(const CDStreamConfig &config, void *data,
        }
 }
 
-static void read_custom_data_mcols(const ICompoundProperty &prop,
+static void read_custom_data_mcols(const ICompoundProperty &arbGeomParams,
                                    const PropertyHeader &prop_header,
                                    const CDStreamConfig &config,
                                    const Alembic::Abc::ISampleSelector &iss)
 {
        C3fArraySamplePtr c3f_ptr = C3fArraySamplePtr();
        C4fArraySamplePtr c4f_ptr = C4fArraySamplePtr();
+       bool use_c3f_ptr;
+       bool is_facevarying;
 
+       /* Find the correct interpretation of the data */
        if (IC3fGeomParam::matches(prop_header)) {
-               IC3fGeomParam color_param(prop, prop_header.getName());
+               IC3fGeomParam color_param(arbGeomParams, prop_header.getName());
                IC3fGeomParam::Sample sample;
+               BLI_assert(!strcmp("rgb", color_param.getInterpretation()));
+
                color_param.getIndexed(sample, iss);
+               is_facevarying = sample.getScope() == kFacevaryingScope &&
+                                config.totloop == sample.getIndices()->size();
 
                c3f_ptr = sample.getVals();
+               use_c3f_ptr = true;
        }
        else if (IC4fGeomParam::matches(prop_header)) {
-               IC4fGeomParam color_param(prop, prop_header.getName());
+               IC4fGeomParam color_param(arbGeomParams, prop_header.getName());
                IC4fGeomParam::Sample sample;
+               BLI_assert(!strcmp("rgba", color_param.getInterpretation()));
+
                color_param.getIndexed(sample, iss);
+               is_facevarying = sample.getScope() == kFacevaryingScope &&
+                                config.totloop == sample.getIndices()->size();
 
                c4f_ptr = sample.getVals();
+               use_c3f_ptr = false;
+       }
+       else {
+               /* this won't happen due to the checks in read_custom_data() */
+               return;
        }
+       BLI_assert(c3f_ptr || c4f_ptr);
 
+       /* Read the vertex colors */
        void *cd_data = config.add_customdata_cb(config.user_data,
                                                 prop_header.getName().c_str(),
                                                 CD_MLOOPCOL);
+       MCol *cfaces = static_cast<MCol *>(cd_data);
+       MPoly *mpolys = config.mpoly;
+       MLoop *mloops = config.mloop;
+
+       size_t face_index = 0;
+       size_t color_index;
+       for (int i = 0; i < config.totpoly; ++i) {
+               MPoly *poly = &mpolys[i];
+               MCol *cface = &cfaces[poly->loopstart + poly->totloop];
+               MLoop *mloop = &mloops[poly->loopstart + poly->totloop];
+
+               for (int j = 0; j < poly->totloop; ++j, ++face_index) {
+                       --cface;
+                       --mloop;
+                       color_index = is_facevarying ? face_index : mloop->v;
 
-       read_mcols(config, cd_data, c3f_ptr, c4f_ptr);
+                       if (use_c3f_ptr) {
+                               const Imath::C3f &color = (*c3f_ptr)[color_index];
+                               cface->a = FTOCHAR(color[0]);
+                               cface->r = FTOCHAR(color[1]);
+                               cface->g = FTOCHAR(color[2]);
+                               cface->b = 255;
+                       }
+                       else {
+                               const Imath::C4f &color = (*c4f_ptr)[color_index];
+                               cface->a = FTOCHAR(color[0]);
+                               cface->r = FTOCHAR(color[1]);
+                               cface->g = FTOCHAR(color[2]);
+                               cface->b = FTOCHAR(color[3]);
+                       }
+               }
+       }
 }
 
 static void read_custom_data_uvs(const ICompoundProperty &prop,
index ef5ae37233372fd4db183c92cee9a8d52212047f..358b8a3c758b6536d6197208fcb6083b54886a97 100644 (file)
@@ -31,7 +31,7 @@ import bpy
 args = None
 
 
-class SimpleImportTest(unittest.TestCase):
+class AbstractAlembicTest(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
         cls.testdir = args.testdir
@@ -43,6 +43,18 @@ class SimpleImportTest(unittest.TestCase):
         # Make sure we always start with a known-empty file.
         bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "empty.blend"))
 
+    def assertAlmostEqualFloatArray(self, actual, expect, places=6, delta=None):
+        """Asserts that the arrays of floats are almost equal."""
+
+        self.assertEqual(len(actual), len(expect),
+                         'Actual array has %d items, expected %d' % (len(actual), len(expect)))
+
+        for idx, (act, exp) in enumerate(zip(actual, expect)):
+            self.assertAlmostEqual(act, exp, places=places, delta=delta,
+                                   msg='%f != %f at index %d' % (act, exp, idx))
+
+
+class SimpleImportTest(AbstractAlembicTest):
     def test_import_cube_hierarchy(self):
         res = bpy.ops.wm.alembic_import(
             filepath=str(self.testdir / "cubes-hierarchy.abc"),
@@ -158,6 +170,38 @@ class SimpleImportTest(unittest.TestCase):
         self.assertEqual('CubeShape', bpy.data.objects['Cube'].data.name)
 
 
+class VertexColourImportTest(AbstractAlembicTest):
+    def test_import_from_houdini(self):
+        # Houdini saved "face-varying", and as RGB.
+        res = bpy.ops.wm.alembic_import(
+            filepath=str(self.testdir / "vertex-colours-houdini.abc"),
+            as_background_job=False)
+        self.assertEqual({'FINISHED'}, res)
+
+        ob = bpy.context.active_object
+        layer = ob.data.vertex_colors['Cf']  # MeshLoopColorLayer
+
+        # Test some known-good values.
+        self.assertAlmostEqualFloatArray(layer.data[0].color, (0, 0, 0))
+        self.assertAlmostEqualFloatArray(layer.data[98].color, (0.9019607, 0.4745098, 0.2666666))
+        self.assertAlmostEqualFloatArray(layer.data[99].color, (0.8941176, 0.4705882, 0.2627451))
+
+    def test_import_from_blender(self):
+        # Blender saved per-vertex, and as RGBA.
+        res = bpy.ops.wm.alembic_import(
+            filepath=str(self.testdir / "vertex-colours-blender.abc"),
+            as_background_job=False)
+        self.assertEqual({'FINISHED'}, res)
+
+        ob = bpy.context.active_object
+        layer = ob.data.vertex_colors['Cf']  # MeshLoopColorLayer
+
+        # Test some known-good values.
+        self.assertAlmostEqualFloatArray(layer.data[0].color, (1.0, 0.0156862, 0.3607843))
+        self.assertAlmostEqualFloatArray(layer.data[98].color, (0.0941176, 0.1215686, 0.9137254))
+        self.assertAlmostEqualFloatArray(layer.data[99].color, (0.1294117, 0.3529411, 0.7529411))
+
+
 def main():
     global args
     import argparse