All in all, this patch adds an Alembic importer, an Alembic exporter,
and a new CacheFile data block which, for now, wraps around an Alembic
archive. This data block is made available through a new modifier ("Mesh
Sequence Cache") as well as a new constraint ("Transform Cache") to
somewhat properly support respectively geometric and transformation data
streaming from alembic caches.
A more in-depth documentation is to be found on the wiki, as well as a
guide to compile alembic: https://wiki.blender.org/index.php/
User:Kevindietrich/AlembicBasicIo.
Many thanks to everyone involved in this little project, and huge shout
out to "cgstrive" for the thorough testings with Maya, 3ds Max, Houdini
and Realflow as well as @fjuhec, @jensverwiebe and @jasperge for the
custom builds and compile fixes.
Reviewers: sergey, campbellbarton, mont29
Reviewed By: sergey, campbellbarton, mont29
Differential Revision: https://developer.blender.org/D2060
380 lines
10 KiB
C++
380 lines
10 KiB
C++
/*
|
|
* ***** BEGIN GPL LICENSE BLOCK *****
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* The Original Code is Copyright (C) 2016 Kévin Dietrich.
|
|
* All rights reserved.
|
|
*
|
|
* ***** END GPL LICENSE BLOCK *****
|
|
*
|
|
*/
|
|
|
|
#include "abc_customdata.h"
|
|
|
|
#include <Alembic/AbcGeom/All.h>
|
|
#include <algorithm>
|
|
|
|
extern "C" {
|
|
#include "DNA_customdata_types.h"
|
|
#include "DNA_meshdata_types.h"
|
|
|
|
#include "BKE_customdata.h"
|
|
}
|
|
|
|
/* NOTE: for now only UVs and Vertex Colors are supported for streaming.
|
|
* Although Alembic only allows for a single UV layer per {I|O}Schema, and does
|
|
* not have a vertex color concept, there is a convention between DCCs to write
|
|
* such data in a way that lets other DCC know what they are for. See comments
|
|
* in the write code for the conventions. */
|
|
|
|
using Alembic::AbcGeom::kVertexScope;
|
|
using Alembic::AbcGeom::kFacevaryingScope;
|
|
|
|
using Alembic::Abc::C4fArraySample;
|
|
using Alembic::Abc::UInt32ArraySample;
|
|
using Alembic::Abc::V2fArraySample;
|
|
|
|
using Alembic::AbcGeom::OV2fGeomParam;
|
|
using Alembic::AbcGeom::OC4fGeomParam;
|
|
|
|
static void get_uvs(const CDStreamConfig &config,
|
|
std::vector<Imath::V2f> &uvs,
|
|
std::vector<uint32_t> &uvidx,
|
|
void *cd_data)
|
|
{
|
|
MLoopUV *mloopuv_array = static_cast<MLoopUV *>(cd_data);
|
|
|
|
if (!mloopuv_array) {
|
|
return;
|
|
}
|
|
|
|
const int num_poly = config.totpoly;
|
|
MPoly *polygons = config.mpoly;
|
|
|
|
if (!config.pack_uvs) {
|
|
int cnt = 0;
|
|
uvidx.resize(config.totloop);
|
|
uvs.resize(config.totloop);
|
|
|
|
for (int i = 0; i < num_poly; ++i) {
|
|
MPoly ¤t_poly = polygons[i];
|
|
MLoopUV *loopuvpoly = mloopuv_array + current_poly.loopstart + current_poly.totloop;
|
|
|
|
for (int j = 0; j < current_poly.totloop; ++j, ++cnt) {
|
|
--loopuvpoly;
|
|
|
|
uvidx[cnt] = cnt;
|
|
uvs[cnt][0] = loopuvpoly->uv[0];
|
|
uvs[cnt][1] = loopuvpoly->uv[1];
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (int i = 0; i < num_poly; ++i) {
|
|
MPoly ¤t_poly = polygons[i];
|
|
MLoopUV *loopuvpoly = mloopuv_array + current_poly.loopstart + current_poly.totloop;
|
|
|
|
for (int j = 0; j < current_poly.totloop; ++j) {
|
|
loopuvpoly--;
|
|
Imath::V2f uv(loopuvpoly->uv[0], loopuvpoly->uv[1]);
|
|
|
|
std::vector<Imath::V2f>::iterator it = std::find(uvs.begin(), uvs.end(), uv);
|
|
|
|
if (it == uvs.end()) {
|
|
uvidx.push_back(uvs.size());
|
|
uvs.push_back(uv);
|
|
}
|
|
else {
|
|
uvidx.push_back(std::distance(uvs.begin(), it));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const char *get_uv_sample(UVSample &sample, const CDStreamConfig &config, CustomData *data)
|
|
{
|
|
const int active_uvlayer = CustomData_get_active_layer(data, CD_MLOOPUV);
|
|
|
|
if (active_uvlayer < 0) {
|
|
return "";
|
|
}
|
|
|
|
void *cd_data = CustomData_get_layer_n(data, CD_MLOOPUV, active_uvlayer);
|
|
|
|
get_uvs(config, sample.uvs, sample.indices, cd_data);
|
|
|
|
return CustomData_get_layer_name(data, CD_MLOOPUV, active_uvlayer);
|
|
}
|
|
|
|
/* Convention to write UVs:
|
|
* - V2fGeomParam on the arbGeomParam
|
|
* - set scope as face varying
|
|
* - (optional due to its behaviour) tag as UV using Alembic::AbcGeom::SetIsUV
|
|
*/
|
|
static void write_uv(const OCompoundProperty &prop, const CDStreamConfig &config, void *data, const char *name)
|
|
{
|
|
std::vector<uint32_t> indices;
|
|
std::vector<Imath::V2f> uvs;
|
|
|
|
get_uvs(config, uvs, indices, data);
|
|
|
|
if (indices.empty() || uvs.empty()) {
|
|
return;
|
|
}
|
|
|
|
OV2fGeomParam param(prop, name, true, kFacevaryingScope, 1);
|
|
|
|
OV2fGeomParam::Sample sample(
|
|
V2fArraySample(&uvs.front(), uvs.size()),
|
|
UInt32ArraySample(&indices.front(), indices.size()),
|
|
kFacevaryingScope);
|
|
|
|
param.set(sample);
|
|
}
|
|
|
|
/* Convention to write Vertex Colors:
|
|
* - C3fGeomParam/C4fGeomParam on the arbGeomParam
|
|
* - set scope as vertex varying
|
|
*/
|
|
static void write_mcol(const OCompoundProperty &prop, const CDStreamConfig &config, void *data, const char *name)
|
|
{
|
|
const float cscale = 1.0f / 255.0f;
|
|
MPoly *polys = config.mpoly;
|
|
MLoop *mloops = config.mloop;
|
|
MCol *cfaces = static_cast<MCol *>(data);
|
|
|
|
std::vector<Imath::C4f> buffer(config.totvert);
|
|
|
|
Imath::C4f col;
|
|
|
|
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--;
|
|
|
|
col[0] = cface->a * cscale;
|
|
col[1] = cface->r * cscale;
|
|
col[2] = cface->g * cscale;
|
|
col[3] = cface->b * cscale;
|
|
|
|
buffer[mloop->v] = col;
|
|
}
|
|
}
|
|
|
|
OC4fGeomParam param(prop, name, true, kFacevaryingScope, 1);
|
|
|
|
OC4fGeomParam::Sample sample(
|
|
C4fArraySample(&buffer.front(), buffer.size()),
|
|
kVertexScope);
|
|
|
|
param.set(sample);
|
|
}
|
|
|
|
void write_custom_data(const OCompoundProperty &prop, const CDStreamConfig &config, CustomData *data, int data_type)
|
|
{
|
|
CustomDataType cd_data_type = static_cast<CustomDataType>(data_type);
|
|
|
|
if (!CustomData_has_layer(data, cd_data_type)) {
|
|
return;
|
|
}
|
|
|
|
const int active_layer = CustomData_get_active_layer(data, cd_data_type);
|
|
const int tot_layers = CustomData_number_of_layers(data, cd_data_type);
|
|
|
|
for (int i = 0; i < tot_layers; ++i) {
|
|
void *cd_data = CustomData_get_layer_n(data, cd_data_type, i);
|
|
const char *name = CustomData_get_layer_name(data, cd_data_type, i);
|
|
|
|
if (cd_data_type == CD_MLOOPUV) {
|
|
/* Already exported. */
|
|
if (i == active_layer) {
|
|
continue;
|
|
}
|
|
|
|
write_uv(prop, config, cd_data, name);
|
|
}
|
|
else if (cd_data_type == CD_MLOOPCOL) {
|
|
write_mcol(prop, config, cd_data, name);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ************************************************************************** */
|
|
|
|
using Alembic::Abc::C3fArraySamplePtr;
|
|
using Alembic::Abc::C4fArraySamplePtr;
|
|
using Alembic::Abc::PropertyHeader;
|
|
|
|
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;
|
|
|
|
if (c3f_ptr) {
|
|
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--;
|
|
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 if (c4f_ptr) {
|
|
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--;
|
|
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,
|
|
const Alembic::AbcGeom::UInt32ArraySamplePtr &indices)
|
|
{
|
|
MPoly *mpolys = config.mpoly;
|
|
MLoopUV *mloopuvs = static_cast<MLoopUV *>(data);
|
|
|
|
unsigned int uv_index, loop_index;
|
|
|
|
for (int i = 0; i < config.totpoly; ++i) {
|
|
MPoly &poly = mpolys[i];
|
|
|
|
for (int f = 0; f < poly.totloop; ++f) {
|
|
loop_index = poly.loopstart + f;
|
|
uv_index = (*indices)[loop_index];
|
|
const Imath::V2f &uv = (*uvs)[uv_index];
|
|
|
|
MLoopUV &loopuv = mloopuvs[loop_index];
|
|
loopuv.uv[0] = uv[0];
|
|
loopuv.uv[1] = uv[1];
|
|
}
|
|
}
|
|
}
|
|
|
|
static void read_custom_data_ex(const ICompoundProperty &prop,
|
|
const PropertyHeader &prop_header,
|
|
const CDStreamConfig &config,
|
|
const Alembic::Abc::ISampleSelector &iss,
|
|
int data_type)
|
|
{
|
|
if (data_type == CD_MLOOPCOL) {
|
|
C3fArraySamplePtr c3f_ptr = C3fArraySamplePtr();
|
|
C4fArraySamplePtr c4f_ptr = C4fArraySamplePtr();
|
|
|
|
if (IC3fGeomParam::matches(prop_header)) {
|
|
IC3fGeomParam color_param(prop, prop_header.getName());
|
|
IC3fGeomParam::Sample sample;
|
|
color_param.getIndexed(sample, iss);
|
|
|
|
c3f_ptr = sample.getVals();
|
|
}
|
|
else if (IC4fGeomParam::matches(prop_header)) {
|
|
IC4fGeomParam color_param(prop, prop_header.getName());
|
|
IC4fGeomParam::Sample sample;
|
|
color_param.getIndexed(sample, iss);
|
|
|
|
c4f_ptr = sample.getVals();
|
|
}
|
|
|
|
void *cd_data = config.add_customdata_cb(config.user_data,
|
|
prop_header.getName().c_str(),
|
|
data_type);
|
|
|
|
read_mcols(config, cd_data, c3f_ptr, c4f_ptr);
|
|
}
|
|
else if (data_type == CD_MLOOPUV) {
|
|
IV2fGeomParam uv_param(prop, prop_header.getName());
|
|
IV2fGeomParam::Sample sample;
|
|
uv_param.getIndexed(sample, iss);
|
|
|
|
if (uv_param.getScope() != kFacevaryingScope) {
|
|
return;
|
|
}
|
|
|
|
void *cd_data = config.add_customdata_cb(config.user_data,
|
|
prop_header.getName().c_str(),
|
|
data_type);
|
|
|
|
read_uvs(config, cd_data, sample.getVals(), sample.getIndices());
|
|
}
|
|
}
|
|
|
|
void read_custom_data(const ICompoundProperty &prop, const CDStreamConfig &config, const Alembic::Abc::ISampleSelector &iss)
|
|
{
|
|
if (!prop.valid()) {
|
|
return;
|
|
}
|
|
|
|
int num_uvs = 0;
|
|
int num_colors = 0;
|
|
|
|
const size_t num_props = prop.getNumProperties();
|
|
|
|
for (size_t i = 0; i < num_props; ++i) {
|
|
const Alembic::Abc::PropertyHeader &prop_header = prop.getPropertyHeader(i);
|
|
|
|
/* Read UVs according to convention. */
|
|
if (IV2fGeomParam::matches(prop_header) && Alembic::AbcGeom::isUV(prop_header)) {
|
|
if (++num_uvs > MAX_MTFACE) {
|
|
continue;
|
|
}
|
|
|
|
read_custom_data_ex(prop, prop_header, config, iss, CD_MLOOPUV);
|
|
continue;
|
|
}
|
|
|
|
/* Read vertex colors according to convention. */
|
|
if (IC3fGeomParam::matches(prop_header) || IC4fGeomParam::matches(prop_header)) {
|
|
if (++num_colors > MAX_MCOL) {
|
|
continue;
|
|
}
|
|
|
|
read_custom_data_ex(prop, prop_header, config, iss, CD_MLOOPCOL);
|
|
continue;
|
|
}
|
|
}
|
|
}
|