/* * 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. */ /** \file * \ingroup balembic */ #include "abc_object.h" #include "abc_util.h" extern "C" { #include "DNA_cachefile_types.h" #include "DNA_constraint_types.h" #include "DNA_modifier_types.h" #include "DNA_object_types.h" #include "DNA_space_types.h" /* for FILE_MAX */ #include "BKE_constraint.h" #include "BKE_idprop.h" #include "BKE_library.h" #include "BKE_modifier.h" #include "BKE_object.h" #include "BLI_listbase.h" #include "BLI_math.h" #include "BLI_string.h" } using Alembic::AbcGeom::IObject; using Alembic::AbcGeom::IXform; using Alembic::AbcGeom::IXformSchema; using Alembic::AbcGeom::OCompoundProperty; using Alembic::AbcGeom::ODoubleArrayProperty; using Alembic::AbcGeom::ODoubleProperty; using Alembic::AbcGeom::OFloatArrayProperty; using Alembic::AbcGeom::OFloatProperty; using Alembic::AbcGeom::OInt32ArrayProperty; using Alembic::AbcGeom::OInt32Property; using Alembic::AbcGeom::OStringArrayProperty; using Alembic::AbcGeom::OStringProperty; /* ************************************************************************** */ AbcObjectWriter::AbcObjectWriter(Object *ob, uint32_t time_sampling, ExportSettings &settings, AbcObjectWriter *parent) : m_object(ob), m_settings(settings), m_time_sampling(time_sampling), m_first_frame(true) { m_name = get_id_name(m_object) + "Shape"; if (parent) { parent->addChild(this); } } AbcObjectWriter::~AbcObjectWriter() { } void AbcObjectWriter::addChild(AbcObjectWriter *child) { m_children.push_back(child); } Imath::Box3d AbcObjectWriter::bounds() { BoundBox *bb = BKE_object_boundbox_get(this->m_object); if (!bb) { if (this->m_object->type != OB_CAMERA) { ABC_LOG(m_settings.logger) << "Bounding box is null!\n"; } return Imath::Box3d(); } /* Convert Z-up to Y-up. This also changes which vector goes into which min/max property. */ this->m_bounds.min.x = bb->vec[0][0]; this->m_bounds.min.y = bb->vec[0][2]; this->m_bounds.min.z = -bb->vec[6][1]; this->m_bounds.max.x = bb->vec[6][0]; this->m_bounds.max.y = bb->vec[6][2]; this->m_bounds.max.z = -bb->vec[0][1]; return this->m_bounds; } void AbcObjectWriter::write() { do_write(); m_first_frame = false; } /* ************************************************************************** */ AbcObjectReader::AbcObjectReader(const IObject &object, ImportSettings &settings) : m_name(""), m_object_name(""), m_data_name(""), m_object(NULL), m_iobject(object), m_settings(&settings), m_min_time(std::numeric_limits::max()), m_max_time(std::numeric_limits::min()), m_refcount(0), parent_reader(NULL) { m_name = object.getFullName(); std::vector parts; split(m_name, '/', parts); if (parts.size() >= 2) { m_object_name = parts[parts.size() - 2]; m_data_name = parts[parts.size() - 1]; } else { m_object_name = m_data_name = parts[parts.size() - 1]; } determine_inherits_xform(); } /* Determine whether we can inherit our parent's XForm */ void AbcObjectReader::determine_inherits_xform() { m_inherits_xform = false; IXform ixform = xform(); if (!ixform) { return; } const IXformSchema &schema(ixform.getSchema()); if (!schema.valid()) { std::cerr << "Alembic object " << ixform.getFullName() << " has an invalid schema." << std::endl; return; } m_inherits_xform = schema.getInheritsXforms(); IObject ixform_parent = ixform.getParent(); if (!ixform_parent.getParent()) { /* The archive top object certainly is not a transform itself, so handle * it as "no parent". */ m_inherits_xform = false; } else { m_inherits_xform = ixform_parent && m_inherits_xform; } } AbcObjectReader::~AbcObjectReader() { } const IObject &AbcObjectReader::iobject() const { return m_iobject; } Object *AbcObjectReader::object() const { return m_object; } void AbcObjectReader::object(Object *ob) { m_object = ob; } static Imath::M44d blend_matrices(const Imath::M44d &m0, const Imath::M44d &m1, const float weight) { float mat0[4][4], mat1[4][4], ret[4][4]; /* Cannot use Imath::M44d::getValue() since this returns a pointer to * doubles and interp_m4_m4m4 expects pointers to floats. So need to convert * the matrices manually. */ for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { mat0[i][j] = static_cast(m0[i][j]); } } for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { mat1[i][j] = static_cast(m1[i][j]); } } interp_m4_m4m4(ret, mat0, mat1, weight); Imath::M44d m; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { m[i][j] = ret[i][j]; } } return m; } Imath::M44d get_matrix(const IXformSchema &schema, const float time) { Alembic::AbcGeom::index_t i0, i1; Alembic::AbcGeom::XformSample s0, s1; const float weight = get_weight_and_index( time, schema.getTimeSampling(), schema.getNumSamples(), i0, i1); schema.get(s0, Alembic::AbcGeom::ISampleSelector(i0)); if (i0 != i1) { schema.get(s1, Alembic::AbcGeom::ISampleSelector(i1)); return blend_matrices(s0.getMatrix(), s1.getMatrix(), weight); } return s0.getMatrix(); } struct Mesh *AbcObjectReader::read_mesh(struct Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &UNUSED(sample_sel), int UNUSED(read_flag), const char **UNUSED(err_str)) { return existing_mesh; } bool AbcObjectReader::topology_changed(Mesh * /*existing_mesh*/, const Alembic::Abc::ISampleSelector & /*sample_sel*/) { /* The default implementation of read_mesh() just returns the original mesh, so never changes the * topology. */ return false; } void AbcObjectReader::setupObjectTransform(const float time) { bool is_constant = false; this->read_matrix(m_object->obmat, time, m_settings->scale, is_constant); invert_m4_m4(m_object->imat, m_object->obmat); BKE_object_apply_mat4(m_object, m_object->obmat, false, false); if (!is_constant) { bConstraint *con = BKE_constraint_add_for_object( m_object, NULL, CONSTRAINT_TYPE_TRANSFORM_CACHE); bTransformCacheConstraint *data = static_cast(con->data); BLI_strncpy(data->object_path, m_iobject.getFullName().c_str(), FILE_MAX); data->cache_file = m_settings->cache_file; id_us_plus(&data->cache_file->id); } } Alembic::AbcGeom::IXform AbcObjectReader::xform() { /* Check that we have an empty object (locator, bone head/tail...). */ if (IXform::matches(m_iobject.getMetaData())) { try { return IXform(m_iobject, Alembic::AbcGeom::kWrapExisting); } catch (Alembic::Util::Exception &ex) { printf("Alembic: error reading object transform for '%s': %s\n", m_iobject.getFullName().c_str(), ex.what()); return IXform(); } } /* Check that we have an object with actual data, in which case the * parent Alembic object should contain the transform. */ IObject abc_parent = m_iobject.getParent(); /* The archive's top object can be recognised by not having a parent. */ if (abc_parent.getParent() && IXform::matches(abc_parent.getMetaData())) { try { return IXform(abc_parent, Alembic::AbcGeom::kWrapExisting); } catch (Alembic::Util::Exception &ex) { printf("Alembic: error reading object transform for '%s': %s\n", abc_parent.getFullName().c_str(), ex.what()); return IXform(); } } /* This can happen in certain cases. For example, MeshLab exports * point clouds without parent XForm. */ return IXform(); } void AbcObjectReader::read_matrix(float r_mat[4][4], const float time, const float scale, bool &is_constant) { IXform ixform = xform(); if (!ixform) { unit_m4(r_mat); is_constant = true; return; } const IXformSchema &schema(ixform.getSchema()); if (!schema.valid()) { std::cerr << "Alembic object " << ixform.getFullName() << " has an invalid schema." << std::endl; return; } const Imath::M44d matrix = get_matrix(schema, time); convert_matrix(matrix, m_object, r_mat); if (m_inherits_xform) { /* In this case, the matrix in Alembic is in local coordinates, so * convert to world matrix. To prevent us from reading and accumulating * all parent matrices in the Alembic file, we assume that the Blender * parent object is already updated for the current timekey, and use its * world matrix. */ if (m_object->parent) { mul_m4_m4m4(r_mat, m_object->parent->obmat, r_mat); } else { /* This can happen if the user deleted the parent object, but also if the Alembic parent was * not imported (because of unknown/unsupported schema, for example). In that case just use * the local matrix as if it is the world matrix. This allows us to import Alembic files from * MeshRoom, see T61935. */ } } else { /* Only apply scaling to root objects, parenting will propagate it. */ float scale_mat[4][4]; scale_m4_fl(scale_mat, scale); mul_m4_m4m4(r_mat, scale_mat, r_mat); } is_constant = schema.isConstant(); } void AbcObjectReader::addCacheModifier() { ModifierData *md = modifier_new(eModifierType_MeshSequenceCache); BLI_addtail(&m_object->modifiers, md); MeshSeqCacheModifierData *mcmd = reinterpret_cast(md); mcmd->cache_file = m_settings->cache_file; id_us_plus(&mcmd->cache_file->id); BLI_strncpy(mcmd->object_path, m_iobject.getFullName().c_str(), FILE_MAX); } chrono_t AbcObjectReader::minTime() const { return m_min_time; } chrono_t AbcObjectReader::maxTime() const { return m_max_time; } int AbcObjectReader::refcount() const { return m_refcount; } void AbcObjectReader::incref() { m_refcount++; } void AbcObjectReader::decref() { m_refcount--; BLI_assert(m_refcount >= 0); }