Blender  V2.93
abc_customdata.cc
Go to the documentation of this file.
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2016 Kévin Dietrich.
17  * All rights reserved.
18  */
19 
24 #include "abc_customdata.h"
25 
26 #include <Alembic/AbcGeom/All.h>
27 #include <algorithm>
28 #include <unordered_map>
29 
30 #include "DNA_customdata_types.h"
31 #include "DNA_meshdata_types.h"
32 
33 #include "BLI_math_base.h"
34 #include "BLI_utildefines.h"
35 
36 #include "BKE_customdata.h"
37 
38 /* NOTE: for now only UVs and Vertex Colors are supported for streaming.
39  * Although Alembic only allows for a single UV layer per {I|O}Schema, and does
40  * not have a vertex color concept, there is a convention between DCCs to write
41  * such data in a way that lets other DCC know what they are for. See comments
42  * in the write code for the conventions. */
43 
44 using Alembic::AbcGeom::kFacevaryingScope;
45 using Alembic::AbcGeom::kVertexScope;
46 
47 using Alembic::Abc::C4fArraySample;
48 using Alembic::Abc::UInt32ArraySample;
49 using Alembic::Abc::V2fArraySample;
50 
51 using Alembic::AbcGeom::OC4fGeomParam;
52 using Alembic::AbcGeom::OV2fGeomParam;
53 namespace blender::io::alembic {
54 
55 static void get_uvs(const CDStreamConfig &config,
56  std::vector<Imath::V2f> &uvs,
57  std::vector<uint32_t> &uvidx,
58  void *cd_data)
59 {
60  MLoopUV *mloopuv_array = static_cast<MLoopUV *>(cd_data);
61 
62  if (!mloopuv_array) {
63  return;
64  }
65 
66  const int num_poly = config.totpoly;
67  MPoly *polygons = config.mpoly;
68  MLoop *mloop = config.mloop;
69 
70  if (!config.pack_uvs) {
71  int cnt = 0;
72  uvidx.resize(config.totloop);
73  uvs.resize(config.totloop);
74 
75  /* Iterate in reverse order to match exported polygons. */
76  for (int i = 0; i < num_poly; i++) {
77  MPoly &current_poly = polygons[i];
78  MLoopUV *loopuv = mloopuv_array + current_poly.loopstart + current_poly.totloop;
79 
80  for (int j = 0; j < current_poly.totloop; j++, cnt++) {
81  loopuv--;
82 
83  uvidx[cnt] = cnt;
84  uvs[cnt][0] = loopuv->uv[0];
85  uvs[cnt][1] = loopuv->uv[1];
86  }
87  }
88  }
89  else {
90  /* Mapping for indexed UVs, deduplicating UV coordinates at vertices. */
91  std::vector<std::vector<uint32_t>> idx_map(config.totvert);
92  int idx_count = 0;
93 
94  for (int i = 0; i < num_poly; i++) {
95  MPoly &current_poly = polygons[i];
96  MLoop *looppoly = mloop + current_poly.loopstart + current_poly.totloop;
97  MLoopUV *loopuv = mloopuv_array + current_poly.loopstart + current_poly.totloop;
98 
99  for (int j = 0; j < current_poly.totloop; j++) {
100  looppoly--;
101  loopuv--;
102 
103  Imath::V2f uv(loopuv->uv[0], loopuv->uv[1]);
104  bool found_same = false;
105 
106  /* Find UV already in uvs array. */
107  for (uint32_t uv_idx : idx_map[looppoly->v]) {
108  if (uvs[uv_idx] == uv) {
109  found_same = true;
110  uvidx.push_back(uv_idx);
111  break;
112  }
113  }
114 
115  /* UV doesn't exists for this vertex, add it. */
116  if (!found_same) {
117  uint32_t uv_idx = idx_count++;
118  idx_map[looppoly->v].push_back(uv_idx);
119  uvidx.push_back(uv_idx);
120  uvs.push_back(uv);
121  }
122  }
123  }
124  }
125 }
126 
128 {
129  const int active_uvlayer = CustomData_get_active_layer(data, CD_MLOOPUV);
130 
131  if (active_uvlayer < 0) {
132  return "";
133  }
134 
135  void *cd_data = CustomData_get_layer_n(data, CD_MLOOPUV, active_uvlayer);
136 
137  get_uvs(config, sample.uvs, sample.indices, cd_data);
138 
139  return CustomData_get_layer_name(data, CD_MLOOPUV, active_uvlayer);
140 }
141 
142 /* Convention to write UVs:
143  * - V2fGeomParam on the arbGeomParam
144  * - set scope as face varying
145  * - (optional due to its behavior) tag as UV using Alembic::AbcGeom::SetIsUV
146  */
147 static void write_uv(const OCompoundProperty &prop,
148  CDStreamConfig &config,
149  void *data,
150  const char *name)
151 {
152  std::vector<uint32_t> indices;
153  std::vector<Imath::V2f> uvs;
154 
155  get_uvs(config, uvs, indices, data);
156 
157  if (indices.empty() || uvs.empty()) {
158  return;
159  }
160 
161  std::string uv_map_name(name);
162  OV2fGeomParam param = config.abc_uv_maps[uv_map_name];
163 
164  if (!param.valid()) {
165  param = OV2fGeomParam(prop, name, true, kFacevaryingScope, 1);
166  }
167  OV2fGeomParam::Sample sample(V2fArraySample(&uvs.front(), uvs.size()),
168  UInt32ArraySample(&indices.front(), indices.size()),
169  kFacevaryingScope);
170  param.set(sample);
171 
172  config.abc_uv_maps[uv_map_name] = param;
173 }
174 
175 /* Convention to write Vertex Colors:
176  * - C3fGeomParam/C4fGeomParam on the arbGeomParam
177  * - set scope as vertex varying
178  */
179 static void write_mcol(const OCompoundProperty &prop,
180  const CDStreamConfig &config,
181  void *data,
182  const char *name)
183 {
184  const float cscale = 1.0f / 255.0f;
185  MPoly *polys = config.mpoly;
186  MLoop *mloops = config.mloop;
187  MCol *cfaces = static_cast<MCol *>(data);
188 
189  std::vector<Imath::C4f> buffer;
190  std::vector<uint32_t> indices;
191 
192  buffer.reserve(config.totvert);
193  indices.reserve(config.totvert);
194 
195  Imath::C4f col;
196 
197  for (int i = 0; i < config.totpoly; i++) {
198  MPoly *p = &polys[i];
199  MCol *cface = &cfaces[p->loopstart + p->totloop];
200  MLoop *mloop = &mloops[p->loopstart + p->totloop];
201 
202  for (int j = 0; j < p->totloop; j++) {
203  cface--;
204  mloop--;
205 
206  col[0] = cface->a * cscale;
207  col[1] = cface->r * cscale;
208  col[2] = cface->g * cscale;
209  col[3] = cface->b * cscale;
210 
211  buffer.push_back(col);
212  indices.push_back(buffer.size() - 1);
213  }
214  }
215 
216  OC4fGeomParam param(prop, name, true, kFacevaryingScope, 1);
217 
218  OC4fGeomParam::Sample sample(C4fArraySample(&buffer.front(), buffer.size()),
219  UInt32ArraySample(&indices.front(), indices.size()),
220  kVertexScope);
221 
222  param.set(sample);
223 }
224 
225 void write_custom_data(const OCompoundProperty &prop,
226  CDStreamConfig &config,
227  CustomData *data,
228  int data_type)
229 {
230  CustomDataType cd_data_type = static_cast<CustomDataType>(data_type);
231 
232  if (!CustomData_has_layer(data, cd_data_type)) {
233  return;
234  }
235 
236  const int active_layer = CustomData_get_active_layer(data, cd_data_type);
237  const int tot_layers = CustomData_number_of_layers(data, cd_data_type);
238 
239  for (int i = 0; i < tot_layers; i++) {
240  void *cd_data = CustomData_get_layer_n(data, cd_data_type, i);
241  const char *name = CustomData_get_layer_name(data, cd_data_type, i);
242 
243  if (cd_data_type == CD_MLOOPUV) {
244  /* Already exported. */
245  if (i == active_layer) {
246  continue;
247  }
248 
249  write_uv(prop, config, cd_data, name);
250  }
251  else if (cd_data_type == CD_MLOOPCOL) {
252  write_mcol(prop, config, cd_data, name);
253  }
254  }
255 }
256 
257 /* ************************************************************************** */
258 
259 using Alembic::Abc::C3fArraySamplePtr;
260 using Alembic::Abc::C4fArraySamplePtr;
261 using Alembic::Abc::PropertyHeader;
262 
263 using Alembic::AbcGeom::IC3fGeomParam;
264 using Alembic::AbcGeom::IC4fGeomParam;
265 using Alembic::AbcGeom::IV2fGeomParam;
266 
267 static void read_uvs(const CDStreamConfig &config,
268  void *data,
269  const Alembic::AbcGeom::V2fArraySamplePtr &uvs,
270  const Alembic::AbcGeom::UInt32ArraySamplePtr &indices)
271 {
272  MPoly *mpolys = config.mpoly;
273  MLoopUV *mloopuvs = static_cast<MLoopUV *>(data);
274 
275  unsigned int uv_index, loop_index, rev_loop_index;
276 
277  for (int i = 0; i < config.totpoly; i++) {
278  MPoly &poly = mpolys[i];
279  unsigned int rev_loop_offset = poly.loopstart + poly.totloop - 1;
280 
281  for (int f = 0; f < poly.totloop; f++) {
282  loop_index = poly.loopstart + f;
283  rev_loop_index = rev_loop_offset - f;
284  uv_index = (*indices)[loop_index];
285  const Imath::V2f &uv = (*uvs)[uv_index];
286 
287  MLoopUV &loopuv = mloopuvs[rev_loop_index];
288  loopuv.uv[0] = uv[0];
289  loopuv.uv[1] = uv[1];
290  }
291  }
292 }
293 
294 static size_t mcols_out_of_bounds_check(const size_t color_index,
295  const size_t array_size,
296  const std::string &iobject_full_name,
297  const PropertyHeader &prop_header,
298  bool &r_is_out_of_bounds,
299  bool &r_bounds_warning_given)
300 {
301  if (color_index < array_size) {
302  return color_index;
303  }
304 
305  if (!r_bounds_warning_given) {
306  std::cerr << "Alembic: color index out of bounds "
307  "reading face colors for object "
308  << iobject_full_name << ", property " << prop_header.getName() << std::endl;
309  r_bounds_warning_given = true;
310  }
311  r_is_out_of_bounds = true;
312  return 0;
313 }
314 
315 static void read_custom_data_mcols(const std::string &iobject_full_name,
316  const ICompoundProperty &arbGeomParams,
317  const PropertyHeader &prop_header,
318  const CDStreamConfig &config,
319  const Alembic::Abc::ISampleSelector &iss)
320 {
321  C3fArraySamplePtr c3f_ptr = C3fArraySamplePtr();
322  C4fArraySamplePtr c4f_ptr = C4fArraySamplePtr();
323  Alembic::Abc::UInt32ArraySamplePtr indices;
324  bool use_c3f_ptr;
325  bool is_facevarying;
326 
327  /* Find the correct interpretation of the data */
328  if (IC3fGeomParam::matches(prop_header)) {
329  IC3fGeomParam color_param(arbGeomParams, prop_header.getName());
330  IC3fGeomParam::Sample sample;
331  BLI_assert(STREQ("rgb", color_param.getInterpretation()));
332 
333  color_param.getIndexed(sample, iss);
334  is_facevarying = sample.getScope() == kFacevaryingScope &&
335  config.totloop == sample.getIndices()->size();
336 
337  c3f_ptr = sample.getVals();
338  indices = sample.getIndices();
339  use_c3f_ptr = true;
340  }
341  else if (IC4fGeomParam::matches(prop_header)) {
342  IC4fGeomParam color_param(arbGeomParams, prop_header.getName());
343  IC4fGeomParam::Sample sample;
344  BLI_assert(STREQ("rgba", color_param.getInterpretation()));
345 
346  color_param.getIndexed(sample, iss);
347  is_facevarying = sample.getScope() == kFacevaryingScope &&
348  config.totloop == sample.getIndices()->size();
349 
350  c4f_ptr = sample.getVals();
351  indices = sample.getIndices();
352  use_c3f_ptr = false;
353  }
354  else {
355  /* this won't happen due to the checks in read_custom_data() */
356  return;
357  }
358  BLI_assert(c3f_ptr || c4f_ptr);
359 
360  /* Read the vertex colors */
361  void *cd_data = config.add_customdata_cb(
362  config.mesh, prop_header.getName().c_str(), CD_MLOOPCOL);
363  MCol *cfaces = static_cast<MCol *>(cd_data);
364  MPoly *mpolys = config.mpoly;
365  MLoop *mloops = config.mloop;
366 
367  size_t face_index = 0;
368  size_t color_index;
369  bool bounds_warning_given = false;
370 
371  /* The colors can go through two layers of indexing. Often the 'indices'
372  * array doesn't do anything (i.e. indices[n] = n), but when it does, it's
373  * important. Blender 2.79 writes indices incorrectly (see T53745), which
374  * is why we have to check for indices->size() > 0 */
375  bool use_dual_indexing = is_facevarying && indices->size() > 0;
376 
377  for (int i = 0; i < config.totpoly; i++) {
378  MPoly *poly = &mpolys[i];
379  MCol *cface = &cfaces[poly->loopstart + poly->totloop];
380  MLoop *mloop = &mloops[poly->loopstart + poly->totloop];
381 
382  for (int j = 0; j < poly->totloop; j++, face_index++) {
383  cface--;
384  mloop--;
385 
386  color_index = is_facevarying ? face_index : mloop->v;
387  if (use_dual_indexing) {
388  color_index = (*indices)[color_index];
389  }
390  if (use_c3f_ptr) {
391  bool is_mcols_out_of_bounds = false;
392  color_index = mcols_out_of_bounds_check(color_index,
393  c3f_ptr->size(),
394  iobject_full_name,
395  prop_header,
396  is_mcols_out_of_bounds,
397  bounds_warning_given);
398  if (is_mcols_out_of_bounds) {
399  continue;
400  }
401  const Imath::C3f &color = (*c3f_ptr)[color_index];
402  cface->a = unit_float_to_uchar_clamp(color[0]);
403  cface->r = unit_float_to_uchar_clamp(color[1]);
404  cface->g = unit_float_to_uchar_clamp(color[2]);
405  cface->b = 255;
406  }
407  else {
408  bool is_mcols_out_of_bounds = false;
409  color_index = mcols_out_of_bounds_check(color_index,
410  c4f_ptr->size(),
411  iobject_full_name,
412  prop_header,
413  is_mcols_out_of_bounds,
414  bounds_warning_given);
415  if (is_mcols_out_of_bounds) {
416  continue;
417  }
418  const Imath::C4f &color = (*c4f_ptr)[color_index];
419  cface->a = unit_float_to_uchar_clamp(color[0]);
420  cface->r = unit_float_to_uchar_clamp(color[1]);
421  cface->g = unit_float_to_uchar_clamp(color[2]);
422  cface->b = unit_float_to_uchar_clamp(color[3]);
423  }
424  }
425  }
426 }
427 
428 static void read_custom_data_uvs(const ICompoundProperty &prop,
429  const PropertyHeader &prop_header,
430  const CDStreamConfig &config,
431  const Alembic::Abc::ISampleSelector &iss)
432 {
433  IV2fGeomParam uv_param(prop, prop_header.getName());
434 
435  if (!uv_param.isIndexed()) {
436  return;
437  }
438 
439  IV2fGeomParam::Sample sample;
440  uv_param.getIndexed(sample, iss);
441 
442  if (uv_param.getScope() != kFacevaryingScope) {
443  return;
444  }
445 
446  void *cd_data = config.add_customdata_cb(config.mesh, prop_header.getName().c_str(), CD_MLOOPUV);
447 
448  read_uvs(config, cd_data, sample.getVals(), sample.getIndices());
449 }
450 
451 void read_custom_data(const std::string &iobject_full_name,
452  const ICompoundProperty &prop,
453  const CDStreamConfig &config,
454  const Alembic::Abc::ISampleSelector &iss)
455 {
456  if (!prop.valid()) {
457  return;
458  }
459 
460  int num_uvs = 0;
461  int num_colors = 0;
462 
463  const size_t num_props = prop.getNumProperties();
464 
465  for (size_t i = 0; i < num_props; i++) {
466  const Alembic::Abc::PropertyHeader &prop_header = prop.getPropertyHeader(i);
467 
468  /* Read UVs according to convention. */
469  if (IV2fGeomParam::matches(prop_header) && Alembic::AbcGeom::isUV(prop_header)) {
470  if (++num_uvs > MAX_MTFACE) {
471  continue;
472  }
473 
474  read_custom_data_uvs(prop, prop_header, config, iss);
475  continue;
476  }
477 
478  /* Read vertex colors according to convention. */
479  if (IC3fGeomParam::matches(prop_header) || IC4fGeomParam::matches(prop_header)) {
480  if (++num_colors > MAX_MCOL) {
481  continue;
482  }
483 
484  read_custom_data_mcols(iobject_full_name, prop, prop_header, config, iss);
485  continue;
486  }
487  }
488 }
489 
490 } // namespace blender::io::alembic
CustomData interface, see also DNA_customdata_types.h.
const char * CustomData_get_layer_name(const struct CustomData *data, int type, int n)
int CustomData_number_of_layers(const struct CustomData *data, int type)
bool CustomData_has_layer(const struct CustomData *data, int type)
int CustomData_get_active_layer(const struct CustomData *data, int type)
void * CustomData_get_layer_n(const struct CustomData *data, int type, int n)
#define BLI_assert(a)
Definition: BLI_assert.h:58
#define STREQ(a, b)
#define MAX_MCOL
CustomDataType
@ CD_MLOOPCOL
@ CD_MLOOPUV
#define MAX_MTFACE
static ushort indices[]
uint col
__kernel void ccl_constant KernelData ccl_global void ccl_global char ccl_global int ccl_global char ccl_global unsigned int ccl_global float * buffer
MINLINE unsigned char unit_float_to_uchar_clamp(float val)
static void sample(SocketReader *reader, int x, int y, float color[4])
static void read_uvs(const CDStreamConfig &config, void *data, const Alembic::AbcGeom::V2fArraySamplePtr &uvs, const Alembic::AbcGeom::UInt32ArraySamplePtr &indices)
const char * get_uv_sample(UVSample &sample, const CDStreamConfig &config, CustomData *data)
static void read_custom_data_mcols(const std::string &iobject_full_name, const ICompoundProperty &arbGeomParams, const PropertyHeader &prop_header, const CDStreamConfig &config, const Alembic::Abc::ISampleSelector &iss)
static void write_uv(const OCompoundProperty &prop, CDStreamConfig &config, void *data, const char *name)
void write_custom_data(const OCompoundProperty &prop, CDStreamConfig &config, CustomData *data, int data_type)
void read_custom_data(const std::string &iobject_full_name, const ICompoundProperty &prop, const CDStreamConfig &config, const Alembic::Abc::ISampleSelector &iss)
static void get_uvs(const CDStreamConfig &config, std::vector< Imath::V2f > &uvs, std::vector< uint32_t > &uvidx, void *cd_data)
static void write_mcol(const OCompoundProperty &prop, const CDStreamConfig &config, void *data, const char *name)
static void read_custom_data_uvs(const ICompoundProperty &prop, const PropertyHeader &prop_header, const CDStreamConfig &config, const Alembic::Abc::ISampleSelector &iss)
static size_t mcols_out_of_bounds_check(const size_t color_index, const size_t array_size, const std::string &iobject_full_name, const PropertyHeader &prop_header, bool &r_is_out_of_bounds, bool &r_bounds_warning_given)
unsigned int uint32_t
Definition: stdint.h:83
unsigned char r
unsigned char a
unsigned char g
unsigned char b
unsigned int v
void *(* add_customdata_cb)(Mesh *mesh, const char *name, int data_type)
std::map< std::string, Alembic::AbcGeom::OV2fGeomParam > abc_uv_maps