diff options
author | Sybren A. Stüvel <sybren@stuvel.eu> | 2017-04-07 18:28:22 +0300 |
---|---|---|
committer | Sybren A. Stüvel <sybren@stuvel.eu> | 2017-04-07 18:28:22 +0300 |
commit | 063bae4fcc079d0ba7b7f86d327179df36a95146 (patch) | |
tree | f484ae3d53406ecc01f90189665defb4b1f73b13 | |
parent | 711ac03fa14a0087d72e2429140357f6748b6972 (diff) | |
parent | 43a910abce50963d12c0ccde596347459d921b9a (diff) |
Merge branch 'master' into blender2.8
# Conflicts:
# source/blender/alembic/intern/abc_exporter.h
# source/blender/alembic/intern/abc_util.cc
24 files changed, 922 insertions, 575 deletions
diff --git a/build_files/cmake/macros.cmake b/build_files/cmake/macros.cmake index 7b47578cbc7..27728917af5 100644 --- a/build_files/cmake/macros.cmake +++ b/build_files/cmake/macros.cmake @@ -595,6 +595,7 @@ function(SETUP_BLENDER_SORTED_LIBS) bf_freestyle bf_ikplugin bf_modifiers + bf_alembic bf_bmesh bf_gpu bf_draw @@ -615,7 +616,6 @@ function(SETUP_BLENDER_SORTED_LIBS) bf_imbuf_openimageio bf_imbuf_dds bf_collada - bf_alembic bf_intern_elbeem bf_intern_memutil bf_intern_guardedalloc diff --git a/intern/cycles/blender/blender_mesh.cpp b/intern/cycles/blender/blender_mesh.cpp index e0e89cec65c..54571b1fea1 100644 --- a/intern/cycles/blender/blender_mesh.cpp +++ b/intern/cycles/blender/blender_mesh.cpp @@ -560,6 +560,9 @@ static void attr_create_pointiness(Scene *scene, return; } const int num_verts = b_mesh.vertices.length(); + if(num_verts == 0) { + return; + } /* STEP 1: Find out duplicated vertices and point duplicates to a single * original vertex. */ @@ -1164,8 +1167,8 @@ void BlenderSync::sync_mesh_motion(BL::Object& b_ob, } /* skip empty meshes */ - size_t numverts = mesh->verts.size(); - size_t numkeys = mesh->curve_keys.size(); + const size_t numverts = mesh->verts.size(); + const size_t numkeys = mesh->curve_keys.size(); if(!numverts && !numkeys) return; @@ -1223,13 +1226,12 @@ void BlenderSync::sync_mesh_motion(BL::Object& b_ob, /* TODO(sergey): Perform preliminary check for number of verticies. */ if(numverts) { - /* find attributes */ + /* Find attributes. */ Attribute *attr_mP = mesh->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION); Attribute *attr_mN = mesh->attributes.find(ATTR_STD_MOTION_VERTEX_NORMAL); Attribute *attr_N = mesh->attributes.find(ATTR_STD_VERTEX_NORMAL); bool new_attribute = false; - - /* add new attributes if they don't exist already */ + /* Add new attributes if they don't exist already. */ if(!attr_mP) { attr_mP = mesh->attributes.add(ATTR_STD_MOTION_VERTEX_POSITION); if(attr_N) @@ -1237,22 +1239,21 @@ void BlenderSync::sync_mesh_motion(BL::Object& b_ob, new_attribute = true; } - - /* load vertex data from mesh */ + /* Load vertex data from mesh. */ float3 *mP = attr_mP->data_float3() + time_index*numverts; float3 *mN = (attr_mN)? attr_mN->data_float3() + time_index*numverts: NULL; - + /* NOTE: We don't copy more that existing amount of vertices to prevent + * possible memory corruption. + */ BL::Mesh::vertices_iterator v; int i = 0; - for(b_mesh.vertices.begin(v); v != b_mesh.vertices.end() && i < numverts; ++v, ++i) { mP[i] = get_float3(v->co()); if(mN) mN[i] = get_float3(v->normal()); } - - /* in case of new attribute, we verify if there really was any motion */ if(new_attribute) { + /* In case of new attribute, we verify if there really was any motion. */ if(b_mesh.vertices.length() != numverts || memcmp(mP, &mesh->verts[0], sizeof(float3)*numverts) == 0) { @@ -1275,7 +1276,6 @@ void BlenderSync::sync_mesh_motion(BL::Object& b_ob, * they had no motion, but we need them anyway now */ float3 *P = &mesh->verts[0]; float3 *N = (attr_N)? attr_N->data_float3(): NULL; - for(int step = 0; step < time_index; step++) { memcpy(attr_mP->data_float3() + step*numverts, P, sizeof(float3)*numverts); if(attr_mN) @@ -1283,6 +1283,16 @@ void BlenderSync::sync_mesh_motion(BL::Object& b_ob, } } } + else { + if(b_mesh.vertices.length() != numverts) { + VLOG(1) << "Topology differs, discarding motion blur for object " + << b_ob.name() << " at time " << time_index; + memcpy(mP, &mesh->verts[0], sizeof(float3)*numverts); + if(mN != NULL) { + memcpy(mN, attr_N->data_float3(), sizeof(float3)*numverts); + } + } + } } /* hair motion */ diff --git a/intern/cycles/device/device_cpu.cpp b/intern/cycles/device/device_cpu.cpp index 2761d9488ca..3c481bb2b39 100644 --- a/intern/cycles/device/device_cpu.cpp +++ b/intern/cycles/device/device_cpu.cpp @@ -856,7 +856,7 @@ int2 CPUSplitKernel::split_kernel_local_size() } int2 CPUSplitKernel::split_kernel_global_size(device_memory& /*kg*/, device_memory& /*data*/, DeviceTask * /*task*/) { - return make_int2(64, 1); + return make_int2(1, 1); } uint64_t CPUSplitKernel::state_buffer_size(device_memory& kernel_globals, device_memory& /*data*/, size_t num_threads) { diff --git a/intern/cycles/device/device_split_kernel.cpp b/intern/cycles/device/device_split_kernel.cpp index ae462a560b7..fa641161c05 100644 --- a/intern/cycles/device/device_split_kernel.cpp +++ b/intern/cycles/device/device_split_kernel.cpp @@ -151,7 +151,8 @@ bool DeviceSplitKernel::path_trace(DeviceTask *task, /* Calculate max groups */ /* Denotes the maximum work groups possible w.r.t. current requested tile size. */ - unsigned int max_work_groups = num_global_elements / WORK_POOL_SIZE + 1; + unsigned int work_pool_size = (device->info.type == DEVICE_CPU) ? WORK_POOL_SIZE_CPU : WORK_POOL_SIZE_GPU; + unsigned int max_work_groups = num_global_elements / work_pool_size + 1; /* Allocate work_pool_wgs memory. */ work_pool_wgs.resize(max_work_groups * sizeof(unsigned int)); @@ -256,10 +257,8 @@ bool DeviceSplitKernel::path_trace(DeviceTask *task, activeRaysAvailable = false; for(int rayStateIter = 0; rayStateIter < global_size[0] * global_size[1]; ++rayStateIter) { - int8_t state = ray_state.get_data()[rayStateIter]; - - if(state != RAY_INACTIVE) { - if(state == RAY_INVALID) { + if(!IS_STATE(ray_state.get_data(), rayStateIter, RAY_INACTIVE)) { + if(IS_STATE(ray_state.get_data(), rayStateIter, RAY_INVALID)) { /* Something went wrong, abort to avoid looping endlessly. */ device->set_error("Split kernel error: invalid ray state"); return false; diff --git a/intern/cycles/device/opencl/opencl_util.cpp b/intern/cycles/device/opencl/opencl_util.cpp index 8128fcee09b..6dca642f3f3 100644 --- a/intern/cycles/device/opencl/opencl_util.cpp +++ b/intern/cycles/device/opencl/opencl_util.cpp @@ -281,6 +281,7 @@ void OpenCLDeviceBase::OpenCLProgram::add_log(string msg, bool debug) } else if(!debug) { printf("%s\n", msg.c_str()); + fflush(stdout); } else { VLOG(2) << msg; diff --git a/intern/cycles/kernel/kernel_shadow.h b/intern/cycles/kernel/kernel_shadow.h index 0426e0a62c9..db6f839d9ed 100644 --- a/intern/cycles/kernel/kernel_shadow.h +++ b/intern/cycles/kernel/kernel_shadow.h @@ -422,9 +422,9 @@ ccl_device_inline bool shadow_blocked(KernelGlobals *kg, return false; } #ifdef __SHADOW_TRICKS__ - const int skip_object = state->catcher_object; + const int skip_object = state->catcher_object; #else - const int skip_object = OBJECT_NONE; + const int skip_object = OBJECT_NONE; #endif /* Do actual shadow shading. */ /* First of all, we check if integrator requires transparent shadows. diff --git a/intern/cycles/kernel/kernel_types.h b/intern/cycles/kernel/kernel_types.h index 19c91248922..623f3728c69 100644 --- a/intern/cycles/kernel/kernel_types.h +++ b/intern/cycles/kernel/kernel_types.h @@ -56,7 +56,13 @@ CCL_NAMESPACE_BEGIN #define VOLUME_STACK_SIZE 16 -#define WORK_POOL_SIZE 64 +#define WORK_POOL_SIZE_GPU 64 +#define WORK_POOL_SIZE_CPU 1 +#ifdef __KERNEL_GPU__ +# define WORK_POOL_SIZE WORK_POOL_SIZE_GPU +#else +# define WORK_POOL_SIZE WORK_POOL_SIZE_CPU +#endif /* device capabilities */ #ifdef __KERNEL_CPU__ diff --git a/intern/locale/msgfmt.cc b/intern/locale/msgfmt.cc index 6ee1ee14781..02c58ebc5bc 100644 --- a/intern/locale/msgfmt.cc +++ b/intern/locale/msgfmt.cc @@ -42,18 +42,6 @@ bool starts_with(const std::string &str, } } -std::string ltrim(const std::string &str) { - std::string result = str; - result.erase(0, result.find_first_not_of(" \t\r\n")); - return result; -} - -std::string rtrim(const std::string &str) { - std::string result = str; - result.erase(result.find_last_not_of(" \t\r\n") + 1); - return result; -} - std::string trim(const std::string &str) { std::string result = str; result.erase(0, result.find_first_not_of(" \t\r\n")); diff --git a/source/blender/alembic/intern/abc_exporter.cc b/source/blender/alembic/intern/abc_exporter.cc index 31d946ddc1c..61ad76b409c 100644 --- a/source/blender/alembic/intern/abc_exporter.cc +++ b/source/blender/alembic/intern/abc_exporter.cc @@ -154,11 +154,13 @@ AbcExporter::AbcExporter(Scene *scene, const char *filename, ExportSettings &set AbcExporter::~AbcExporter() { - std::map<std::string, AbcTransformWriter*>::iterator it, e; - for (it = m_xforms.begin(), e = m_xforms.end(); it != e; ++it) { - delete it->second; + /* Free xforms map */ + m_xforms_type::iterator it_x, e_x; + for (it_x = m_xforms.begin(), e_x = m_xforms.end(); it_x != e_x; ++it_x) { + delete it_x->second; } + /* Free shapes vector */ for (int i = 0, e = m_shapes.size(); i != e; ++i) { delete m_shapes[i]; } @@ -323,7 +325,7 @@ void AbcExporter::operator()(Main *bmain, float &progress, bool &was_canceled) continue; } - std::map<std::string, AbcTransformWriter *>::iterator xit, xe; + m_xforms_type::iterator xit, xe; for (xit = m_xforms.begin(), xe = m_xforms.end(); xit != xe; ++xit) { xit->second->write(); } @@ -404,7 +406,7 @@ void AbcExporter::exploreTransform(EvaluationContext *eval_ctx, Base *ob_base, O free_object_duplilist(lb); } -void AbcExporter::createTransformWriter(Object *ob, Object *parent, Object *dupliObParent) +AbcTransformWriter * AbcExporter::createTransformWriter(Object *ob, Object *parent, Object *dupliObParent) { const std::string name = get_object_dag_path_name(ob, dupliObParent); @@ -413,44 +415,47 @@ void AbcExporter::createTransformWriter(Object *ob, Object *parent, Object *dupl BLI_assert(ob != dupliObParent); /* check if we have already created a transform writer for this object */ - if (getXForm(name) != NULL) { - ABC_LOG(m_settings.logger) << "xform " << name << " already exists!\n"; - return; + AbcTransformWriter *my_writer = getXForm(name); + if (my_writer != NULL){ + return my_writer; } - AbcTransformWriter *parent_xform = NULL; + AbcTransformWriter *parent_writer = NULL; + Alembic::Abc::OObject alembic_parent; if (parent) { - const std::string parentname = get_object_dag_path_name(parent, dupliObParent); - parent_xform = getXForm(parentname); - - if (!parent_xform) { - if (parent->parent) { - createTransformWriter(parent, parent->parent, dupliObParent); - } - else if (parent == dupliObParent) { - if (dupliObParent->parent == NULL) { - createTransformWriter(parent, NULL, NULL); - } - else { - createTransformWriter(parent, dupliObParent->parent, dupliObParent->parent); - } + /* Since there are so many different ways to find parents (as evident + * in the number of conditions below), we can't really look up the + * parent by name. We'll just call createTransformWriter(), which will + * return the parent's AbcTransformWriter pointer. */ + if (parent->parent) { + parent_writer = createTransformWriter(parent, parent->parent, dupliObParent); + } + else if (parent == dupliObParent) { + if (dupliObParent->parent == NULL) { + parent_writer = createTransformWriter(parent, NULL, NULL); } else { - createTransformWriter(parent, dupliObParent, dupliObParent); + parent_writer = createTransformWriter(parent, dupliObParent->parent, dupliObParent->parent); } - - parent_xform = getXForm(parentname); } - } + else { + parent_writer = createTransformWriter(parent, dupliObParent, dupliObParent); + } - if (parent_xform) { - m_xforms[name] = new AbcTransformWriter(ob, parent_xform->alembicXform(), parent_xform, m_trans_sampling_index, m_settings); - m_xforms[name]->setParent(parent); + BLI_assert(parent_writer); + alembic_parent = parent_writer->alembicXform(); } else { - m_xforms[name] = new AbcTransformWriter(ob, m_writer->archive().getTop(), NULL, m_trans_sampling_index, m_settings); + /* Parentless objects still have the "top object" as parent + * in Alembic. */ + alembic_parent = m_writer->archive().getTop(); } + + my_writer = new AbcTransformWriter(ob, alembic_parent, parent_writer, + m_trans_sampling_index, m_settings); + m_xforms[name] = my_writer; + return my_writer; } void AbcExporter::createShapeWriters(EvaluationContext *eval_ctx) diff --git a/source/blender/alembic/intern/abc_exporter.h b/source/blender/alembic/intern/abc_exporter.h index cf2a3432da9..f0e8e6b6815 100644 --- a/source/blender/alembic/intern/abc_exporter.h +++ b/source/blender/alembic/intern/abc_exporter.h @@ -92,7 +92,10 @@ class AbcExporter { ArchiveWriter *m_writer; - std::map<std::string, AbcTransformWriter *> m_xforms; + /* mapping from name to transform writer */ + typedef std::map<std::string, AbcTransformWriter *> m_xforms_type; + m_xforms_type m_xforms; + std::vector<AbcObjectWriter *> m_shapes; public: @@ -110,7 +113,7 @@ private: void createTransformWritersHierarchy(EvaluationContext *eval_ctx); void createTransformWritersFlat(); - void createTransformWriter(Object *ob, Object *parent, Object *dupliObParent); + AbcTransformWriter * createTransformWriter(Object *ob, Object *parent, Object *dupliObParent); void exploreTransform(EvaluationContext *eval_ctx, Base *ob_base, Object *parent, Object *dupliObParent); void exploreObject(EvaluationContext *eval_ctx, Base *ob_base, Object *dupliObParent); void createShapeWriters(EvaluationContext *eval_ctx); diff --git a/source/blender/alembic/intern/abc_object.cc b/source/blender/alembic/intern/abc_object.cc index 36873c140ff..9ccd719063c 100644 --- a/source/blender/alembic/intern/abc_object.cc +++ b/source/blender/alembic/intern/abc_object.cc @@ -126,6 +126,7 @@ AbcObjectReader::AbcObjectReader(const IObject &object, ImportSettings &settings , m_min_time(std::numeric_limits<chrono_t>::max()) , m_max_time(std::numeric_limits<chrono_t>::min()) , m_refcount(0) + , parent_reader(NULL) { m_name = object.getFullName(); std::vector<std::string> parts; @@ -213,7 +214,7 @@ Imath::M44d get_matrix(const IXformSchema &schema, const float time) return s0.getMatrix(); } -void AbcObjectReader::readObjectMatrix(const float time) +void AbcObjectReader::setupObjectTransform(const float time) { bool is_constant = false; @@ -235,49 +236,88 @@ void AbcObjectReader::readObjectMatrix(const float time) } } -void AbcObjectReader::read_matrix(float mat[4][4], const float time, const float scale, bool &is_constant) +Alembic::AbcGeom::IXform AbcObjectReader::xform() { - IXform ixform; - bool has_alembic_parent = false; - /* Check that we have an empty object (locator, bone head/tail...). */ if (IXform::matches(m_iobject.getMetaData())) { - ixform = IXform(m_iobject, Alembic::AbcGeom::kWrapExisting); - - /* See comment below. */ - has_alembic_parent = m_iobject.getParent().getParent().valid(); + return IXform(m_iobject, Alembic::AbcGeom::kWrapExisting); } - /* Check that we have an object with actual data. */ - else if (IXform::matches(m_iobject.getParent().getMetaData())) { - ixform = IXform(m_iobject.getParent(), Alembic::AbcGeom::kWrapExisting); - - /* This is a bit hackish, but we need to make sure that extra - * transformations added to the matrix (rotation/scale) are only applied - * to root objects. The way objects and their hierarchy are created will - * need to be revisited at some point but for now this seems to do the - * trick. - * - * Explanation of the trick: - * The first getParent() will return this object's transformation matrix. - * The second getParent() will get the parent of the transform, but this - * might be the archive root ('/') which is valid, so we go passed it to - * make sure that there is no parent. - */ - has_alembic_parent = m_iobject.getParent().getParent().getParent().valid(); + + /* 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())) { + return IXform(abc_parent, Alembic::AbcGeom::kWrapExisting); } + /* Should not happen. */ - else { + std::cerr << "AbcObjectReader::xform(): " + << "unable to find IXform for Alembic object '" + << m_iobject.getFullName() << "'\n"; + BLI_assert(false); + + 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) { return; } - const IXformSchema &schema(ixform.getSchema()); - + const IXformSchema & schema(ixform.getSchema()); if (!schema.valid()) { + std::cerr << "Alembic object " << ixform.getFullName() + << " has an invalid schema." << std::endl; return; } + bool has_alembic_parent; + 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". */ + has_alembic_parent = false; + } + else { + has_alembic_parent = ixform_parent && schema.getInheritsXforms(); + + if (has_alembic_parent && m_object->parent == NULL) { + /* TODO Sybren: This happened in some files. I think I solved it, + * but I'll leave this check in here anyway until we've tested it + * more thoroughly. Better than crashing on a null parent anyway. */ + std::cerr << "Alembic object " << m_iobject.getFullName() + << " with transform " << ixform.getFullName() + << " has an Alembic parent but no parent Blender object." + << std::endl; + has_alembic_parent = false; + } + } + const Imath::M44d matrix = get_matrix(schema, time); - convert_matrix(matrix, m_object, mat, scale, has_alembic_parent); + convert_matrix(matrix, m_object, r_mat); + + if (has_alembic_parent) { + /* 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. */ + BLI_assert(m_object->parent); + mul_m4_m4m4(r_mat, m_object->parent->obmat, r_mat); + } + else { + /* Only apply scaling to root objects, parenting will propagate it. */ + float scale_mat[4][4]; + scale_m4_fl(scale_mat, scale); + scale_mat[3][3] = scale; /* scale translations too */ + mul_m4_m4m4(r_mat, r_mat, scale_mat); + } is_constant = schema.isConstant(); } diff --git a/source/blender/alembic/intern/abc_object.h b/source/blender/alembic/intern/abc_object.h index 0f733e67d3f..d5344533b55 100644 --- a/source/blender/alembic/intern/abc_object.h +++ b/source/blender/alembic/intern/abc_object.h @@ -117,15 +117,7 @@ struct ImportSettings { template <typename Schema> static bool has_animations(Schema &schema, ImportSettings *settings) { - if (settings->is_sequence) { - return true; - } - - if (!schema.isConstant()) { - return true; - } - - return false; + return settings->is_sequence || !schema.isConstant(); } /* ************************************************************************** */ @@ -152,15 +144,30 @@ protected: int m_refcount; public: + AbcObjectReader *parent_reader; + +public: explicit AbcObjectReader(const Alembic::Abc::IObject &object, ImportSettings &settings); virtual ~AbcObjectReader(); const Alembic::Abc::IObject &iobject() const; + typedef std::vector<AbcObjectReader *> ptr_vector; + + /** + * Returns the transform of this object. This can be the Alembic object + * itself (in case of an Empty) or it can be the parent Alembic object. + */ + virtual Alembic::AbcGeom::IXform xform(); + Object *object() const; void object(Object *ob); + const std::string & name() const { return m_name; } + const std::string & object_name() const { return m_object_name; } + const std::string & data_name() const { return m_data_name; } + virtual bool valid() const = 0; virtual void readObjectData(Main *bmain, float time) = 0; @@ -173,7 +180,8 @@ public: return dm; } - void readObjectMatrix(const float time); + /** Reads the object matrix and sets up an object transform if animated. */ + void setupObjectTransform(const float time); void addCacheModifier(); @@ -184,7 +192,8 @@ public: void incref(); void decref(); - void read_matrix(float mat[4][4], const float time, const float scale, bool &is_constant); + void read_matrix(float r_mat[4][4], const float time, + const float scale, bool &is_constant); }; Imath::M44d get_matrix(const Alembic::AbcGeom::IXformSchema &schema, const float time); diff --git a/source/blender/alembic/intern/abc_transform.cc b/source/blender/alembic/intern/abc_transform.cc index 2c6ef09326c..cad1eae4764 100644 --- a/source/blender/alembic/intern/abc_transform.cc +++ b/source/blender/alembic/intern/abc_transform.cc @@ -64,7 +64,6 @@ AbcTransformWriter::AbcTransformWriter(Object *ob, : AbcObjectWriter(NULL, ob, time_sampling, settings, parent) { m_is_animated = hasAnimation(m_object); - m_parent = NULL; if (!m_is_animated) { time_sampling = 0; @@ -86,26 +85,28 @@ void AbcTransformWriter::do_write() return; } - float mat[4][4]; - create_transform_matrix(m_object, mat); + float yup_mat[4][4]; + create_transform_matrix(m_object, yup_mat); /* Only apply rotation to root camera, parenting will propagate it. */ if (m_object->type == OB_CAMERA && !has_parent_camera(m_object)) { float rot_mat[4][4]; axis_angle_to_mat4_single(rot_mat, 'X', -M_PI_2); - mul_m4_m4m4(mat, mat, rot_mat); + mul_m4_m4m4(yup_mat, yup_mat, rot_mat); } if (!m_object->parent) { /* Only apply scaling to root objects, parenting will propagate it. */ + /* TODO Sybren: when we're exporting as "flat", i.e. non-hierarchial, + * we should apply the scale even when the object has a parent + * Blender Object. */ float scale_mat[4][4]; scale_m4_fl(scale_mat, m_settings.global_scale); - mul_m4_m4m4(mat, mat, scale_mat); - mul_v3_fl(mat[3], m_settings.global_scale); + scale_mat[3][3] = m_settings.global_scale; /* also scale translation */ + mul_m4_m4m4(yup_mat, yup_mat, scale_mat); } - m_matrix = convert_matrix(mat); - + m_matrix = convert_matrix(yup_mat); m_sample.setMatrix(m_matrix); m_schema.set(m_sample); } @@ -133,6 +134,10 @@ bool AbcTransformWriter::hasAnimation(Object * /*ob*/) const AbcEmptyReader::AbcEmptyReader(const Alembic::Abc::IObject &object, ImportSettings &settings) : AbcObjectReader(object, settings) { + /* Empties have no data. It makes the import of Alembic files easier to + * understand when we name the empty after its name in Alembic. */ + m_object_name = object.getName(); + Alembic::AbcGeom::IXform xform(object, Alembic::AbcGeom::kWrapExisting); m_schema = xform.getSchema(); @@ -146,6 +151,7 @@ bool AbcEmptyReader::valid() const void AbcEmptyReader::readObjectData(Main *bmain, float /*time*/) { - m_object = BKE_object_add_only_object(bmain, OB_EMPTY, m_data_name.c_str()); + m_object = BKE_object_add_only_object(bmain, OB_EMPTY, + m_object_name.c_str()); m_object->data = NULL; } diff --git a/source/blender/alembic/intern/abc_transform.h b/source/blender/alembic/intern/abc_transform.h index 6a3aae216f2..b55fa12dadf 100644 --- a/source/blender/alembic/intern/abc_transform.h +++ b/source/blender/alembic/intern/abc_transform.h @@ -37,7 +37,6 @@ class AbcTransformWriter : public AbcObjectWriter { Alembic::Abc::M44d m_matrix; bool m_is_animated; - Object *m_parent; bool m_visible; public: @@ -49,7 +48,6 @@ public: Alembic::AbcGeom::OXform &alembicXform() { return m_xform;} virtual Imath::Box3d bounds(); - void setParent(Object *p) { m_parent = p; } private: virtual void do_write(); diff --git a/source/blender/alembic/intern/abc_util.cc b/source/blender/alembic/intern/abc_util.cc index 41cc53b0716..b99be718c18 100644 --- a/source/blender/alembic/intern/abc_util.cc +++ b/source/blender/alembic/intern/abc_util.cc @@ -42,7 +42,7 @@ extern "C" { #include "PIL_time.h" } -std::string get_id_name(Object *ob) +std::string get_id_name(const Object * const ob) { if (!ob) { return ""; @@ -51,7 +51,7 @@ std::string get_id_name(Object *ob) return get_id_name(&ob->id); } -std::string get_id_name(ID *id) +std::string get_id_name(const ID * const id) { std::string name(id->name + 2); std::replace(name.begin(), name.end(), ' ', '_'); @@ -61,7 +61,6 @@ std::string get_id_name(ID *id) return name; } - /** * @brief get_object_dag_path_name returns the name under which the object * will be exported in the Alembic file. It is of the form @@ -71,7 +70,7 @@ std::string get_id_name(ID *id) * @param dupli_parent * @return */ -std::string get_object_dag_path_name(Object *ob, Object *dupli_parent) +std::string get_object_dag_path_name(const Object * const ob, Object *dupli_parent) { std::string name = get_id_name(ob); @@ -121,15 +120,28 @@ void split(const std::string &s, const char delim, std::vector<std::string> &tok } } -/* Create a rotation matrix for each axis from euler angles. - * Euler angles are swaped to change coordinate system. */ -static void create_rotation_matrix( +void create_swapped_rotation_matrix( float rot_x_mat[3][3], float rot_y_mat[3][3], - float rot_z_mat[3][3], const float euler[3], const bool to_yup) + float rot_z_mat[3][3], const float euler[3], + AbcAxisSwapMode mode) { const float rx = euler[0]; - const float ry = (to_yup) ? euler[2] : -euler[2]; - const float rz = (to_yup) ? -euler[1] : euler[1]; + float ry; + float rz; + + /* Apply transformation */ + switch(mode) { + case ABC_ZUP_FROM_YUP: + ry = -euler[2]; + rz = euler[1]; + break; + case ABC_YUP_FROM_ZUP: + ry = euler[2]; + rz = -euler[1]; + break; + default: + BLI_assert(false); + } unit_m3(rot_x_mat); unit_m3(rot_y_mat); @@ -151,58 +163,70 @@ static void create_rotation_matrix( rot_z_mat[1][1] = cos(rz); } -/* Recompute transform matrix of object in new coordinate system - * (from Y-Up to Z-Up). */ -void create_transform_matrix(float r_mat[4][4]) +/* Convert matrix from Z=up to Y=up or vice versa. Use yup_mat = zup_mat for in-place conversion. */ +void copy_m44_axis_swap(float dst_mat[4][4], float src_mat[4][4], AbcAxisSwapMode mode) { - float rot_mat[3][3], rot[3][3], scale_mat[4][4], invmat[4][4], transform_mat[4][4]; + float dst_rot[3][3], src_rot[3][3], dst_scale_mat[4][4]; float rot_x_mat[3][3], rot_y_mat[3][3], rot_z_mat[3][3]; - float loc[3], scale[3], euler[3]; + float src_trans[3], dst_scale[3], src_scale[3], euler[3]; - zero_v3(loc); - zero_v3(scale); + zero_v3(src_trans); + zero_v3(dst_scale); + zero_v3(src_scale); zero_v3(euler); - unit_m3(rot); - unit_m3(rot_mat); - unit_m4(scale_mat); - unit_m4(transform_mat); - unit_m4(invmat); + unit_m3(src_rot); + unit_m3(dst_rot); + unit_m4(dst_scale_mat); - /* Compute rotation matrix. */ + /* We assume there is no sheer component and no homogeneous scaling component. */ + BLI_assert(fabs(src_mat[0][3]) < 2 * FLT_EPSILON); + BLI_assert(fabs(src_mat[1][3]) < 2 * FLT_EPSILON); + BLI_assert(fabs(src_mat[2][3]) < 2 * FLT_EPSILON); + BLI_assert(fabs(src_mat[3][3] - 1.0f) < 2 * FLT_EPSILON); - /* Extract location, rotation, and scale from matrix. */ - mat4_to_loc_rot_size(loc, rot, scale, r_mat); + /* Extract translation, rotation, and scale form matrix. */ + mat4_to_loc_rot_size(src_trans, src_rot, src_scale, src_mat); /* Get euler angles from rotation matrix. */ - mat3_to_eulO(euler, ROT_MODE_XYZ, rot); + mat3_to_eulO(euler, ROT_MODE_XZY, src_rot); /* Create X, Y, Z rotation matrices from euler angles. */ - create_rotation_matrix(rot_x_mat, rot_y_mat, rot_z_mat, euler, false); + create_swapped_rotation_matrix(rot_x_mat, rot_y_mat, rot_z_mat, euler, mode); /* Concatenate rotation matrices. */ - mul_m3_m3m3(rot_mat, rot_mat, rot_y_mat); - mul_m3_m3m3(rot_mat, rot_mat, rot_z_mat); - mul_m3_m3m3(rot_mat, rot_mat, rot_x_mat); + mul_m3_m3m3(dst_rot, dst_rot, rot_z_mat); + mul_m3_m3m3(dst_rot, dst_rot, rot_y_mat); + mul_m3_m3m3(dst_rot, dst_rot, rot_x_mat); - /* Add rotation matrix to transformation matrix. */ - copy_m4_m3(transform_mat, rot_mat); + mat3_to_eulO(euler, ROT_MODE_XZY, dst_rot); - /* Add translation to transformation matrix. */ - copy_zup_from_yup(transform_mat[3], loc); + /* Start construction of dst_mat from rotation matrix */ + unit_m4(dst_mat); + copy_m4_m3(dst_mat, dst_rot); - /* Create scale matrix. */ - scale_mat[0][0] = scale[0]; - scale_mat[1][1] = scale[2]; - scale_mat[2][2] = scale[1]; + /* Apply translation */ + switch(mode) { + case ABC_ZUP_FROM_YUP: + copy_zup_from_yup(dst_mat[3], src_trans); + break; + case ABC_YUP_FROM_ZUP: + copy_yup_from_zup(dst_mat[3], src_trans); + break; + default: + BLI_assert(false); + } - /* Add scale to transformation matrix. */ - mul_m4_m4m4(transform_mat, transform_mat, scale_mat); + /* Apply scale matrix. Swaps y and z, but does not + * negate like translation does. */ + dst_scale[0] = src_scale[0]; + dst_scale[1] = src_scale[2]; + dst_scale[2] = src_scale[1]; - copy_m4_m4(r_mat, transform_mat); + size_to_mat4(dst_scale_mat, dst_scale); + mul_m4_m4m4(dst_mat, dst_mat, dst_scale_mat); } -void convert_matrix(const Imath::M44d &xform, Object *ob, - float r_mat[4][4], float scale, bool has_alembic_parent) +void convert_matrix(const Imath::M44d &xform, Object *ob, float r_mat[4][4]) { for (int i = 0; i < 4; ++i) { for (int j = 0; j < 4; ++j) { @@ -216,207 +240,29 @@ void convert_matrix(const Imath::M44d &xform, Object *ob, mul_m4_m4m4(r_mat, r_mat, cam_to_yup); } - create_transform_matrix(r_mat); - - if (ob->parent) { - mul_m4_m4m4(r_mat, ob->parent->obmat, r_mat); - } - /* TODO(kevin) */ - else if (!has_alembic_parent) { - /* 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, r_mat, scale_mat); - mul_v3_fl(r_mat[3], scale); - } + copy_m44_axis_swap(r_mat, r_mat, ABC_ZUP_FROM_YUP); } -/* Recompute transform matrix of object in new coordinate system (from Z-Up to Y-Up). */ -void create_transform_matrix(Object *obj, float transform_mat[4][4]) +/* Recompute transform matrix of object in new coordinate system + * (from Z-Up to Y-Up). */ +void create_transform_matrix(Object *obj, float r_yup_mat[4][4]) { - float rot_mat[3][3], rot[3][3], scale_mat[4][4], invmat[4][4], mat[4][4]; - float rot_x_mat[3][3], rot_y_mat[3][3], rot_z_mat[3][3]; - float loc[3], scale[3], euler[3]; - - zero_v3(loc); - zero_v3(scale); - zero_v3(euler); - unit_m3(rot); - unit_m3(rot_mat); - unit_m4(scale_mat); - unit_m4(transform_mat); - unit_m4(invmat); - unit_m4(mat); + float zup_mat[4][4]; /* get local matrix. */ + /* TODO Sybren: when we're exporting as "flat", i.e. non-hierarchial, + * we should export the world matrix even when the object has a parent + * Blender Object. */ if (obj->parent) { - invert_m4_m4(invmat, obj->parent->obmat); - mul_m4_m4m4(mat, invmat, obj->obmat); + /* Note that this produces another matrix than the local matrix, due to + * constraints and modifiers as well as the obj->parentinv matrix. */ + invert_m4_m4(obj->parent->imat, obj->parent->obmat); + mul_m4_m4m4(zup_mat, obj->parent->imat, obj->obmat); + copy_m44_axis_swap(r_yup_mat, zup_mat, ABC_YUP_FROM_ZUP); } else { - copy_m4_m4(mat, obj->obmat); + copy_m44_axis_swap(r_yup_mat, obj->obmat, ABC_YUP_FROM_ZUP); } - - /* Compute rotation matrix. */ - switch (obj->rotmode) { - case ROT_MODE_AXISANGLE: - { - /* Get euler angles from axis angle rotation. */ - axis_angle_to_eulO(euler, ROT_MODE_XYZ, obj->rotAxis, obj->rotAngle); - - /* Create X, Y, Z rotation matrices from euler angles. */ - create_rotation_matrix(rot_x_mat, rot_y_mat, rot_z_mat, euler, true); - - /* Concatenate rotation matrices. */ - mul_m3_m3m3(rot_mat, rot_mat, rot_y_mat); - mul_m3_m3m3(rot_mat, rot_mat, rot_z_mat); - mul_m3_m3m3(rot_mat, rot_mat, rot_x_mat); - - /* Extract location and scale from matrix. */ - mat4_to_loc_rot_size(loc, rot, scale, mat); - - break; - } - case ROT_MODE_QUAT: - { - float q[4]; - copy_v4_v4(q, obj->quat); - - /* Swap axis. */ - q[2] = obj->quat[3]; - q[3] = -obj->quat[2]; - - /* Compute rotation matrix from quaternion. */ - quat_to_mat3(rot_mat, q); - - /* Extract location and scale from matrix. */ - mat4_to_loc_rot_size(loc, rot, scale, mat); - - break; - } - case ROT_MODE_XYZ: - { - /* Extract location, rotation, and scale form matrix. */ - mat4_to_loc_rot_size(loc, rot, scale, mat); - - /* Get euler angles from rotation matrix. */ - mat3_to_eulO(euler, ROT_MODE_XYZ, rot); - - /* Create X, Y, Z rotation matrices from euler angles. */ - create_rotation_matrix(rot_x_mat, rot_y_mat, rot_z_mat, euler, true); - - /* Concatenate rotation matrices. */ - mul_m3_m3m3(rot_mat, rot_mat, rot_y_mat); - mul_m3_m3m3(rot_mat, rot_mat, rot_z_mat); - mul_m3_m3m3(rot_mat, rot_mat, rot_x_mat); - - break; - } - case ROT_MODE_XZY: - { - /* Extract location, rotation, and scale form matrix. */ - mat4_to_loc_rot_size(loc, rot, scale, mat); - - /* Get euler angles from rotation matrix. */ - mat3_to_eulO(euler, ROT_MODE_XZY, rot); - - /* Create X, Y, Z rotation matrices from euler angles. */ - create_rotation_matrix(rot_x_mat, rot_y_mat, rot_z_mat, euler, true); - - /* Concatenate rotation matrices. */ - mul_m3_m3m3(rot_mat, rot_mat, rot_z_mat); - mul_m3_m3m3(rot_mat, rot_mat, rot_y_mat); - mul_m3_m3m3(rot_mat, rot_mat, rot_x_mat); - - break; - } - case ROT_MODE_YXZ: - { - /* Extract location, rotation, and scale form matrix. */ - mat4_to_loc_rot_size(loc, rot, scale, mat); - - /* Get euler angles from rotation matrix. */ - mat3_to_eulO(euler, ROT_MODE_YXZ, rot); - - /* Create X, Y, Z rotation matrices from euler angles. */ - create_rotation_matrix(rot_x_mat, rot_y_mat, rot_z_mat, euler, true); - - /* Concatenate rotation matrices. */ - mul_m3_m3m3(rot_mat, rot_mat, rot_y_mat); - mul_m3_m3m3(rot_mat, rot_mat, rot_x_mat); - mul_m3_m3m3(rot_mat, rot_mat, rot_z_mat); - - break; - } - case ROT_MODE_YZX: - { - /* Extract location, rotation, and scale form matrix. */ - mat4_to_loc_rot_size(loc, rot, scale, mat); - - /* Get euler angles from rotation matrix. */ - mat3_to_eulO(euler, ROT_MODE_YZX, rot); - - /* Create X, Y, Z rotation matrices from euler angles. */ - create_rotation_matrix(rot_x_mat, rot_y_mat, rot_z_mat, euler, true); - - /* Concatenate rotation matrices. */ - mul_m3_m3m3(rot_mat, rot_mat, rot_x_mat); - mul_m3_m3m3(rot_mat, rot_mat, rot_y_mat); - mul_m3_m3m3(rot_mat, rot_mat, rot_z_mat); - - break; - } - case ROT_MODE_ZXY: - { - /* Extract location, rotation, and scale form matrix. */ - mat4_to_loc_rot_size(loc, rot, scale, mat); - - /* Get euler angles from rotation matrix. */ - mat3_to_eulO(euler, ROT_MODE_ZXY, rot); - - /* Create X, Y, Z rotation matrices from euler angles. */ - create_rotation_matrix(rot_x_mat, rot_y_mat, rot_z_mat, euler, true); - - /* Concatenate rotation matrices. */ - mul_m3_m3m3(rot_mat, rot_mat, rot_z_mat); - mul_m3_m3m3(rot_mat, rot_mat, rot_x_mat); - mul_m3_m3m3(rot_mat, rot_mat, rot_y_mat); - - break; - } - case ROT_MODE_ZYX: - { - /* Extract location, rotation, and scale form matrix. */ - mat4_to_loc_rot_size(loc, rot, scale, mat); - - /* Get euler angles from rotation matrix. */ - mat3_to_eulO(euler, ROT_MODE_ZYX, rot); - - /* Create X, Y, Z rotation matrices from euler angles. */ - create_rotation_matrix(rot_x_mat, rot_y_mat, rot_z_mat, euler, true); - - /* Concatenate rotation matrices. */ - mul_m3_m3m3(rot_mat, rot_mat, rot_x_mat); - mul_m3_m3m3(rot_mat, rot_mat, rot_z_mat); - mul_m3_m3m3(rot_mat, rot_mat, rot_y_mat); - - break; - } - } - - /* Add rotation matrix to transformation matrix. */ - copy_m4_m3(transform_mat, rot_mat); - - /* Add translation to transformation matrix. */ - copy_yup_from_zup(transform_mat[3], loc); - - /* Create scale matrix. */ - scale_mat[0][0] = scale[0]; - scale_mat[1][1] = scale[2]; - scale_mat[2][2] = scale[1]; - - /* Add scale to transformation matrix. */ - mul_m4_m4m4(transform_mat, transform_mat, scale_mat); } bool has_property(const Alembic::Abc::ICompoundProperty &prop, const std::string &name) @@ -509,7 +355,10 @@ AbcObjectReader *create_reader(const Alembic::AbcGeom::IObject &object, ImportSe reader = new AbcCurveReader(object, settings); } else { - assert(false); + std::cerr << "Alembic: unknown how to handle objects of schema " + << md.get("schemaObjTitle") + << ", skipping object " + << object.getFullName() << std::endl; } return reader; diff --git a/source/blender/alembic/intern/abc_util.h b/source/blender/alembic/intern/abc_util.h index ab2cce59afa..7217d5d5cef 100644 --- a/source/blender/alembic/intern/abc_util.h +++ b/source/blender/alembic/intern/abc_util.h @@ -32,6 +32,11 @@ # define ABC_INLINE static inline #endif +/** + * @brief The CacheReader struct is only used for anonymous pointers, + * to interface between C and C++ code. This library only creates + * pointers to AbcObjectReader (or subclasses thereof). + */ struct CacheReader { int unused; }; @@ -45,15 +50,14 @@ struct ID; struct Object; struct Base; -std::string get_id_name(ID *id); -std::string get_id_name(Object *ob); -std::string get_object_dag_path_name(Object *ob, Object *dupli_parent); +std::string get_id_name(const ID * const id); +std::string get_id_name(const Object * const ob); +std::string get_object_dag_path_name(const Object * const ob, Object *dupli_parent); bool object_selected(const Base * const ob_base); Imath::M44d convert_matrix(float mat[4][4]); -void create_transform_matrix(float r_mat[4][4]); -void create_transform_matrix(Object *obj, float transform_mat[4][4]); +void create_transform_matrix(Object *obj, float r_transform_mat[4][4]); void split(const std::string &s, const char delim, std::vector<std::string> &tokens); @@ -64,8 +68,7 @@ bool begins_with(const TContainer &input, const TContainer &match) && std::equal(match.begin(), match.end(), input.begin()); } -void convert_matrix(const Imath::M44d &xform, Object *ob, - float r_mat[4][4], float scale, bool has_alembic_parent = false); +void convert_matrix(const Imath::M44d &xform, Object *ob, float r_mat[4][4]); template <typename Schema> void get_min_max_time_ex(const Schema &schema, chrono_t &min, chrono_t &max) @@ -118,34 +121,54 @@ AbcObjectReader *create_reader(const Alembic::AbcGeom::IObject &object, ImportSe ABC_INLINE void copy_zup_from_yup(float zup[3], const float yup[3]) { + const float old_yup1 = yup[1]; /* in case zup == yup */ zup[0] = yup[0]; zup[1] = -yup[2]; - zup[2] = yup[1]; + zup[2] = old_yup1; } ABC_INLINE void copy_zup_from_yup(short zup[3], const short yup[3]) { + const short old_yup1 = yup[1]; /* in case zup == yup */ zup[0] = yup[0]; zup[1] = -yup[2]; - zup[2] = yup[1]; + zup[2] = old_yup1; } /* Copy from Z-up to Y-up. */ ABC_INLINE void copy_yup_from_zup(float yup[3], const float zup[3]) { + const float old_zup1 = zup[1]; /* in case yup == zup */ yup[0] = zup[0]; yup[1] = zup[2]; - yup[2] = -zup[1]; + yup[2] = -old_zup1; } ABC_INLINE void copy_yup_from_zup(short yup[3], const short zup[3]) { + const short old_zup1 = zup[1]; /* in case yup == zup */ yup[0] = zup[0]; yup[1] = zup[2]; - yup[2] = -zup[1]; + yup[2] = -old_zup1; } +/* Names are given in (dst, src) order, just like + * the parameters of copy_m44_axis_swap() */ +typedef enum { + ABC_ZUP_FROM_YUP = 1, + ABC_YUP_FROM_ZUP = 2, +} AbcAxisSwapMode; + +/* Create a rotation matrix for each axis from euler angles. + * Euler angles are swaped to change coordinate system. */ +void create_swapped_rotation_matrix( + float rot_x_mat[3][3], float rot_y_mat[3][3], + float rot_z_mat[3][3], const float euler[3], + AbcAxisSwapMode mode); + +void copy_m44_axis_swap(float dst_mat[4][4], float src_mat[4][4], AbcAxisSwapMode mode); + /* *************************** */ #undef ABC_DEBUG_TIME diff --git a/source/blender/alembic/intern/alembic_capi.cc b/source/blender/alembic/intern/alembic_capi.cc index b85344be7f5..b457b38b9fd 100644 --- a/source/blender/alembic/intern/alembic_capi.cc +++ b/source/blender/alembic/intern/alembic_capi.cc @@ -21,6 +21,7 @@ */ #include "../ABC_alembic.h" +#include <boost/foreach.hpp> #include <Alembic/AbcMaterial/IMaterial.h> @@ -122,91 +123,62 @@ ABC_INLINE AbcArchiveHandle *handle_from_archive(ArchiveReader *archive) /* NOTE: this function is similar to visit_objects below, need to keep them in * sync. */ -static void gather_objects_paths(const IObject &object, ListBase *object_paths) +static bool gather_objects_paths(const IObject &object, ListBase *object_paths) { if (!object.valid()) { - return; + return false; } - for (int i = 0; i < object.getNumChildren(); ++i) { - IObject child = object.getChild(i); - if (!child.valid()) { - continue; - } + size_t children_claiming_this_object = 0; + size_t num_children = object.getNumChildren(); - bool get_path = false; + for (size_t i = 0; i < num_children; ++i) { + bool child_claims_this_object = gather_objects_paths(object.getChild(i), object_paths); + children_claiming_this_object += child_claims_this_object ? 1 : 0; + } - const MetaData &md = child.getMetaData(); + const MetaData &md = object.getMetaData(); + bool get_path = false; + bool parent_is_part_of_this_object = false; - if (IXform::matches(md)) { - /* Check whether or not this object is a Maya locator, which is - * similar to empties used as parent object in Blender. */ - if (has_property(child.getProperties(), "locator")) { - get_path = true; - } - else { - /* Avoid creating an empty object if the child of this transform - * is not a transform (that is an empty). */ - if (child.getNumChildren() == 1) { - if (IXform::matches(child.getChild(0).getMetaData())) { - get_path = true; - } -#if 0 - else { - std::cerr << "Skipping " << child.getFullName() << '\n'; - } -#endif - } - else { - get_path = true; - } - } - } - else if (IPolyMesh::matches(md)) { - get_path = true; - } - else if (ISubD::matches(md)) { - get_path = true; - } - else if (INuPatch::matches(md)) { -#ifdef USE_NURBS - get_path = true; -#endif - } - else if (ICamera::matches(md)) { - get_path = true; - } - else if (IPoints::matches(md)) { - get_path = true; - } - else if (IMaterial::matches(md)) { - /* Pass for now. */ - } - else if (ILight::matches(md)) { - /* Pass for now. */ - } - else if (IFaceSet::matches(md)) { - /* Pass, those are handled in the mesh reader. */ - } - else if (ICurves::matches(md)) { + if (!object.getParent()) { + /* The root itself is not an object we should import. */ + } + else if (IXform::matches(md)) { + if (has_property(object.getProperties(), "locator")) { get_path = true; } else { - assert(false); + get_path = children_claiming_this_object == 0; } - if (get_path) { - AlembicObjectPath *abc_path = static_cast<AlembicObjectPath *>( - MEM_callocN(sizeof(AlembicObjectPath), "AlembicObjectPath")); - - BLI_strncpy(abc_path->path, child.getFullName().c_str(), PATH_MAX); + /* Transforms are never "data" for their parent. */ + parent_is_part_of_this_object = false; + } + else { + /* These types are "data" for their parent. */ + get_path = + IPolyMesh::matches(md) || + ISubD::matches(md) || +#ifdef USE_NURBS + INuPatch::matches(md) || +#endif + ICamera::matches(md) || + IPoints::matches(md) || + ICurves::matches(md); + parent_is_part_of_this_object = get_path; + } - BLI_addtail(object_paths, abc_path); - } + if (get_path) { + void *abc_path_void = MEM_callocN(sizeof(AlembicObjectPath), "AlembicObjectPath"); + AlembicObjectPath *abc_path = static_cast<AlembicObjectPath *>(abc_path_void); - gather_objects_paths(child, object_paths); + BLI_strncpy(abc_path->path, object.getFullName().c_str(), PATH_MAX); + BLI_addtail(object_paths, abc_path); } + + return parent_is_part_of_this_object; } AbcArchiveHandle *ABC_create_handle(const char *filename, ListBase *object_paths) @@ -414,114 +386,208 @@ void ABC_export( /* ********************** Import file ********************** */ -static void visit_object(const IObject &object, - std::vector<AbcObjectReader *> &readers, - GHash *parent_map, - ImportSettings &settings) +/** + * Generates an AbcObjectReader for this Alembic object and its children. + * + * \param object The Alembic IObject to visit. + * \param readers The created AbcObjectReader * will be appended to this vector. + * \param settings Import settings, not used directly but passed to the + * AbcObjectReader subclass constructors. + * \param r_assign_as_parent Return parameter, contains a list of reader + * pointers, whose parent pointer should still be set. + * This is filled when this call to visit_object() didn't create + * a reader that should be the parent. + * \return A pair of boolean and reader pointer. The boolean indicates whether + * this IObject claims its parent as part of the same object + * (for example an IPolyMesh object would claim its parent, as the mesh + * is interpreted as the object's data, and the parent IXform as its + * Blender object). The pointer is the AbcObjectReader that represents + * the IObject parameter. + * + * NOTE: this function is similar to gather_object_paths above, need to keep + * them in sync. */ +static std::pair<bool, AbcObjectReader *> visit_object( + const IObject &object, + AbcObjectReader::ptr_vector &readers, + ImportSettings &settings, + AbcObjectReader::ptr_vector &r_assign_as_parent) { + const std::string & full_name = object.getFullName(); + if (!object.valid()) { - return; + std::cerr << " - " + << full_name + << ": object is invalid, skipping it and all its children.\n"; + return std::make_pair(false, static_cast<AbcObjectReader *>(NULL)); } - for (int i = 0; i < object.getNumChildren(); ++i) { - IObject child = object.getChild(i); + /* The interpretation of data by the children determine the role of this + * object. This is especially important for Xform objects, as they can be + * either part of a Blender object or a Blender object (Empty) themselves. + */ + size_t children_claiming_this_object = 0; + size_t num_children = object.getNumChildren(); + AbcObjectReader::ptr_vector claiming_child_readers; + AbcObjectReader::ptr_vector nonclaiming_child_readers; + AbcObjectReader::ptr_vector assign_as_parent; + for (size_t i = 0; i < num_children; ++i) { + const IObject ichild = object.getChild(i); - if (!child.valid()) { - continue; + /* TODO: When we only support C++11, use std::tie() instead. */ + std::pair<bool, AbcObjectReader *> child_result; + child_result = visit_object(ichild, readers, settings, assign_as_parent); + + bool child_claims_this_object = child_result.first; + AbcObjectReader *child_reader = child_result.second; + + if (child_reader == NULL) { + BLI_assert(!child_claims_this_object); + } + else { + if (child_claims_this_object) { + claiming_child_readers.push_back(child_reader); + } else { + nonclaiming_child_readers.push_back(child_reader); + } } - AbcObjectReader *reader = NULL; + children_claiming_this_object += child_claims_this_object ? 1 : 0; + } + BLI_assert(children_claiming_this_object == claiming_child_readers.size()); - const MetaData &md = child.getMetaData(); + AbcObjectReader *reader = NULL; + const MetaData &md = object.getMetaData(); + bool parent_is_part_of_this_object = false; - if (IXform::matches(md)) { - bool create_xform = false; + if (!object.getParent()) { + /* The root itself is not an object we should import. */ + } + else if (IXform::matches(md)) { + bool create_empty; - /* Check whether or not this object is a Maya locator, which is - * similar to empties used as parent object in Blender. */ - if (has_property(child.getProperties(), "locator")) { - create_xform = true; - } - else { - /* Avoid creating an empty object if the child of this transform - * is not a transform (that is an empty). */ - if (child.getNumChildren() == 1) { - if (IXform::matches(child.getChild(0).getMetaData())) { - create_xform = true; - } -#if 0 - else { - std::cerr << "Skipping " << child.getFullName() << '\n'; - } -#endif - } - else { - create_xform = true; - } - } + /* An xform can either be a Blender Object (if it contains a mesh, for + * example), but it can also be an Empty. Its correct translation to + * Blender's data model depends on its children. */ - if (create_xform) { - reader = new AbcEmptyReader(child, settings); - } + /* Check whether or not this object is a Maya locator, which is + * similar to empties used as parent object in Blender. */ + if (has_property(object.getProperties(), "locator")) { + create_empty = true; } - else if (IPolyMesh::matches(md)) { - reader = new AbcMeshReader(child, settings); + else { + create_empty = claiming_child_readers.empty(); } - else if (ISubD::matches(md)) { - reader = new AbcSubDReader(child, settings); + + if (create_empty) { + reader = new AbcEmptyReader(object, settings); } - else if (INuPatch::matches(md)) { + } + else if (IPolyMesh::matches(md)) { + reader = new AbcMeshReader(object, settings); + parent_is_part_of_this_object = true; + } + else if (ISubD::matches(md)) { + reader = new AbcSubDReader(object, settings); + parent_is_part_of_this_object = true; + } + else if (INuPatch::matches(md)) { #ifdef USE_NURBS - /* TODO(kevin): importing cyclic NURBS from other software crashes - * at the moment. This is due to the fact that NURBS in other - * software have duplicated points which causes buffer overflows in - * Blender. Need to figure out exactly how these points are - * duplicated, in all cases (cyclic U, cyclic V, and cyclic UV). - * Until this is fixed, disabling NURBS reading. */ - reader = new AbcNurbsReader(child, settings); + /* TODO(kevin): importing cyclic NURBS from other software crashes + * at the moment. This is due to the fact that NURBS in other + * software have duplicated points which causes buffer overflows in + * Blender. Need to figure out exactly how these points are + * duplicated, in all cases (cyclic U, cyclic V, and cyclic UV). + * Until this is fixed, disabling NURBS reading. */ + reader = new AbcNurbsReader(object, settings); + parent_is_part_of_this_object = true; #endif + } + else if (ICamera::matches(md)) { + reader = new AbcCameraReader(object, settings); + parent_is_part_of_this_object = true; + } + else if (IPoints::matches(md)) { + reader = new AbcPointsReader(object, settings); + parent_is_part_of_this_object = true; + } + else if (IMaterial::matches(md)) { + /* Pass for now. */ + } + else if (ILight::matches(md)) { + /* Pass for now. */ + } + else if (IFaceSet::matches(md)) { + /* Pass, those are handled in the mesh reader. */ + } + else if (ICurves::matches(md)) { + reader = new AbcCurveReader(object, settings); + parent_is_part_of_this_object = true; + } + else { + std::cerr << "Alembic object " << full_name + << " is of unsupported schema type '" + << object.getMetaData().get("schemaObjTitle") << "'" + << std::endl; + } + + if (reader) { + /* We have created a reader, which should imply that this object is + * not claimed as part of any child Alembic object. */ + BLI_assert(claiming_child_readers.empty()); + + readers.push_back(reader); + reader->incref(); + + AlembicObjectPath *abc_path = static_cast<AlembicObjectPath *>( + MEM_callocN(sizeof(AlembicObjectPath), "AlembicObjectPath")); + BLI_strncpy(abc_path->path, full_name.c_str(), PATH_MAX); + BLI_addtail(&settings.cache_file->object_paths, abc_path); + + /* We can now assign this reader as parent for our children. */ + if (nonclaiming_child_readers.size() + assign_as_parent.size() > 0) { + /* TODO: When we only support C++11, use for (a: b) instead. */ + BOOST_FOREACH(AbcObjectReader *child_reader, nonclaiming_child_readers) { + child_reader->parent_reader = reader; + } + BOOST_FOREACH(AbcObjectReader *child_reader, assign_as_parent) { + child_reader->parent_reader = reader; + } } - else if (ICamera::matches(md)) { - reader = new AbcCameraReader(child, settings); - } - else if (IPoints::matches(md)) { - reader = new AbcPointsReader(child, settings); - } - else if (IMaterial::matches(md)) { - /* Pass for now. */ - } - else if (ILight::matches(md)) { - /* Pass for now. */ - } - else if (IFaceSet::matches(md)) { - /* Pass, those are handled in the mesh reader. */ - } - else if (ICurves::matches(md)) { - reader = new AbcCurveReader(child, settings); + } + else if (object.getParent()) { + if (claiming_child_readers.size() > 0) { + /* The first claiming child will serve just fine as parent to + * our non-claiming children. Since all claiming children share + * the same XForm, it doesn't really matter which one we pick. */ + AbcObjectReader *claiming_child = claiming_child_readers[0]; + BOOST_FOREACH(AbcObjectReader *child_reader, nonclaiming_child_readers) { + child_reader->parent_reader = claiming_child; + } + BOOST_FOREACH(AbcObjectReader *child_reader, assign_as_parent) { + child_reader->parent_reader = claiming_child; + } + /* Claiming children should have our parent set as their parent. */ + BOOST_FOREACH(AbcObjectReader *child_reader, claiming_child_readers) { + r_assign_as_parent.push_back(child_reader); + } } else { - assert(false); - } - - if (reader) { - readers.push_back(reader); - reader->incref(); - - AlembicObjectPath *abc_path = static_cast<AlembicObjectPath *>( - MEM_callocN(sizeof(AlembicObjectPath), "AlembicObjectPath")); - - BLI_strncpy(abc_path->path, child.getFullName().c_str(), PATH_MAX); - - BLI_addtail(&settings.cache_file->object_paths, abc_path); - - /* Cast to `void *` explicitly to avoid compiler errors because it - * is a `const char *` which the compiler cast to `const void *` - * instead of the expected `void *`. */ - BLI_ghash_insert(parent_map, (void *)child.getFullName().c_str(), reader); + /* This object isn't claimed by any child, and didn't produce + * a reader. Odd situation, could be the top Alembic object, or + * an unsupported Alembic schema. Delegate to our parent. */ + BOOST_FOREACH(AbcObjectReader *child_reader, claiming_child_readers) { + r_assign_as_parent.push_back(child_reader); + } + BOOST_FOREACH(AbcObjectReader *child_reader, nonclaiming_child_readers) { + r_assign_as_parent.push_back(child_reader); + } + BOOST_FOREACH(AbcObjectReader *child_reader, assign_as_parent) { + r_assign_as_parent.push_back(child_reader); + } } - - visit_object(child, readers, parent_map, settings); } + + return std::make_pair(parent_is_part_of_this_object, reader); } enum { @@ -536,7 +602,6 @@ struct ImportJobData { char filename[1024]; ImportSettings settings; - GHash *parent_map; std::vector<AbcObjectReader *> readers; short *stop; @@ -613,11 +678,12 @@ static void import_startjob(void *user_data, short *stop, short *do_update, floa *data->do_update = true; *data->progress = 0.05f; - data->parent_map = BLI_ghash_str_new("alembic parent ghash"); - /* Parse Alembic Archive. */ + AbcObjectReader::ptr_vector assign_as_parent; + visit_object(archive->getTop(), data->readers, data->settings, assign_as_parent); - visit_object(archive->getTop(), data->readers, data->parent_map, data->settings); + /* There shouldn't be any orphans. */ + BLI_assert(assign_as_parent.size() == 0); if (G.is_break) { data->was_cancelled = true; @@ -641,13 +707,16 @@ static void import_startjob(void *user_data, short *stop, short *do_update, floa if (reader->valid()) { reader->readObjectData(data->bmain, 0.0f); - reader->readObjectMatrix(0.0f); min_time = std::min(min_time, reader->minTime()); max_time = std::max(max_time, reader->maxTime()); } + else { + std::cerr << "Object " << reader->name() << " in Alembic file " + << data->filename << " is invalid.\n"; + } - *data->progress = 0.1f + 0.6f * (++i / size); + *data->progress = 0.1f + 0.3f * (++i / size); *data->do_update = true; if (G.is_break) { @@ -671,39 +740,25 @@ static void import_startjob(void *user_data, short *stop, short *do_update, floa } } - /* Setup parentship. */ - - i = 0; + /* Setup parenthood. */ for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { const AbcObjectReader *reader = *iter; - const AbcObjectReader *parent_reader = NULL; - const IObject &iobject = reader->iobject(); - - IObject parent = iobject.getParent(); + const AbcObjectReader *parent_reader = reader->parent_reader; + Object *ob = reader->object(); - if (!IXform::matches(iobject.getHeader())) { - /* In the case of an non XForm node, the parent is the transform - * matrix of the data itself, so we get the its grand parent. - */ - - /* Special case with object only containing a mesh and some strands, - * we want both objects to be parented to the same object. */ - if (!is_mesh_and_strands(parent)) { - parent = parent.getParent(); - } + if (parent_reader == NULL) { + ob->parent = NULL; } - - parent_reader = reinterpret_cast<AbcObjectReader *>( - BLI_ghash_lookup(data->parent_map, parent.getFullName().c_str())); - - if (parent_reader) { - Object *parent = parent_reader->object(); - - if (parent != NULL && reader->object() != parent) { - Object *ob = reader->object(); - ob->parent = parent; - } + else { + ob->parent = parent_reader->object(); } + } + + /* Setup transformations and constraints. */ + i = 0; + for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { + AbcObjectReader *reader = *iter; + reader->setupObjectTransform(0.0f); *data->progress = 0.7f + 0.3f * (++i / size); *data->do_update = true; @@ -728,10 +783,9 @@ static void import_endjob(void *user_data) for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { Object *ob = (*iter)->object(); - if (ob->data) { - BKE_libblock_free_us(data->bmain, ob->data); - ob->data = NULL; - } + /* It's possible that cancellation occured between the creation of + * the reader and the creation of the Blender object. */ + if (ob == NULL) continue; BKE_libblock_free_us(data->bmain, ob); } @@ -761,10 +815,6 @@ static void import_endjob(void *user_data) } } - if (data->parent_map) { - BLI_ghash_free(data->parent_map, NULL, NULL); - } - switch (data->error_code) { default: case ABC_NO_ERROR: @@ -798,7 +848,6 @@ void ABC_import(bContext *C, const char *filepath, float scale, bool is_sequence job->settings.sequence_len = sequence_len; job->settings.offset = offset; job->settings.validate_meshes = validate_meshes; - job->parent_map = NULL; job->error_code = ABC_NO_ERROR; job->was_cancelled = false; diff --git a/source/blender/bmesh/intern/bmesh_core.c b/source/blender/bmesh/intern/bmesh_core.c index cee5450a37d..4fe14fdf5c9 100644 --- a/source/blender/bmesh/intern/bmesh_core.c +++ b/source/blender/bmesh/intern/bmesh_core.c @@ -2403,15 +2403,20 @@ static void bmesh_kernel_vert_separate__cleanup(BMesh *bm, LinkNode *edges_separ do { LinkNode *n_orig = edges_separate->link; do { - BMEdge *e_orig = n_orig->link; + LinkNode *n_prev = n_orig; LinkNode *n_step = n_orig->next; + BMEdge *e_orig = n_orig->link; do { BMEdge *e = n_step->link; BLI_assert(e != e_orig); - if ((e->v1 == e_orig->v1) && (e->v2 == e_orig->v2)) { - BM_edge_splice(bm, e_orig, e); + if ((e->v1 == e_orig->v1) && (e->v2 == e_orig->v2) && + BM_edge_splice(bm, e_orig, e)) + { + /* don't visit again */ + n_prev->next = n_step->next; } - } while ((n_step = n_step->next)); + } while ((n_prev = n_step), + (n_step = n_step->next)); } while ((n_orig = n_orig->next) && n_orig->next); } while ((edges_separate = edges_separate->next)); diff --git a/source/blender/bmesh/operators/bmo_primitive.c b/source/blender/bmesh/operators/bmo_primitive.c index 723e0b168e0..e5c3ff7a088 100644 --- a/source/blender/bmesh/operators/bmo_primitive.c +++ b/source/blender/bmesh/operators/bmo_primitive.c @@ -1566,7 +1566,7 @@ void BM_mesh_calc_uvs_cone( BLI_assert(cd_loop_uv_offset != -1); /* caller is responsible for ensuring the mesh has UVs */ - x = 0.0f; + x = 1.0f; y = 1.0f - uv_height; BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) { @@ -1580,7 +1580,7 @@ void BM_mesh_calc_uvs_cone( switch (loop_index) { case 0: - x += uv_width; + /* Continue in the last position */ break; case 1: y += uv_height; @@ -1598,8 +1598,6 @@ void BM_mesh_calc_uvs_cone( luv->uv[0] = x; luv->uv[1] = y; } - - x += uv_width; } else { /* top or bottom face - so unwrap it by transforming back to a circle and using the X/Y coords */ diff --git a/source/blender/modifiers/intern/MOD_meshsequencecache.c b/source/blender/modifiers/intern/MOD_meshsequencecache.c index 852d0f949dd..df13cadd184 100644 --- a/source/blender/modifiers/intern/MOD_meshsequencecache.c +++ b/source/blender/modifiers/intern/MOD_meshsequencecache.c @@ -111,7 +111,7 @@ static DerivedMesh *applyModifier(ModifierData *md, Object *ob, if (!mcmd->reader) { mcmd->reader = CacheReader_open_alembic_object(cache_file->handle, - mcmd->reader, + NULL, ob, mcmd->object_path); if (!mcmd->reader) { diff --git a/tests/gtests/CMakeLists.txt b/tests/gtests/CMakeLists.txt index 1d363f31119..ad77b1389f6 100644 --- a/tests/gtests/CMakeLists.txt +++ b/tests/gtests/CMakeLists.txt @@ -14,5 +14,7 @@ if(WITH_GTESTS) add_subdirectory(blenlib) add_subdirectory(guardedalloc) add_subdirectory(bmesh) + if(WITH_ALEMBIC) + add_subdirectory(alembic) + endif() endif() - diff --git a/tests/gtests/alembic/CMakeLists.txt b/tests/gtests/alembic/CMakeLists.txt new file mode 100644 index 00000000000..c1480910d42 --- /dev/null +++ b/tests/gtests/alembic/CMakeLists.txt @@ -0,0 +1,51 @@ +# ***** 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) 2014, Blender Foundation +# All rights reserved. +# +# Contributor(s): Sybren A. Stüvel +# +# ***** END GPL LICENSE BLOCK ***** + +set(INC + . + .. + ../../../source/blender/blenlib + ../../../source/blender/alembic + ${ALEMBIC_INCLUDE_DIRS} + ${BOOST_INCLUDE_DIR} + ${HDF5_INCLUDE_DIRS} + ${OPENEXR_INCLUDE_DIRS} +) + +include_directories(${INC}) + +setup_libdirs() +get_property(BLENDER_SORTED_LIBS GLOBAL PROPERTY BLENDER_SORTED_LIBS_PROP) + +if(WITH_BUILDINFO) + set(_buildinfo_src "$<TARGET_OBJECTS:buildinfoobj>") +else() + set(_buildinfo_src "") +endif() + +# For motivation on doubling BLENDER_SORTED_LIBS, see ../bmesh/CMakeLists.txt +BLENDER_SRC_GTEST(abc_matrix "abc_matrix_test.cc;${_buildinfo_src}" "${BLENDER_SORTED_LIBS};${BLENDER_SORTED_LIBS}") + +unset(_buildinfo_src) + +setup_liblinks(abc_matrix_test) diff --git a/tests/gtests/alembic/abc_matrix_test.cc b/tests/gtests/alembic/abc_matrix_test.cc new file mode 100644 index 00000000000..08bce1ed50f --- /dev/null +++ b/tests/gtests/alembic/abc_matrix_test.cc @@ -0,0 +1,282 @@ +#include "testing/testing.h" + +// Keep first since utildefines defines AT which conflicts with fucking STL +#include "intern/abc_util.h" + +extern "C" { +#include "BLI_utildefines.h" +#include "BLI_math.h" +} + + +TEST(abc_matrix, CreateRotationMatrixY_YfromZ) { + // Input variables + float rot_x_mat[3][3]; + float rot_y_mat[3][3]; + float rot_z_mat[3][3]; + float euler[3] = {0.f, M_PI_4, 0.f}; + + // Construct expected matrices + float unit[3][3]; + float rot_z_min_quart_pi[3][3]; // rotation of -pi/4 radians over z-axis + + unit_m3(unit); + unit_m3(rot_z_min_quart_pi); + rot_z_min_quart_pi[0][0] = M_SQRT1_2; + rot_z_min_quart_pi[0][1] = -M_SQRT1_2; + rot_z_min_quart_pi[1][0] = M_SQRT1_2; + rot_z_min_quart_pi[1][1] = M_SQRT1_2; + + // Run tests + create_swapped_rotation_matrix(rot_x_mat, rot_y_mat, rot_z_mat, euler, + ABC_YUP_FROM_ZUP); + + EXPECT_M3_NEAR(rot_x_mat, unit, 1e-5f); + EXPECT_M3_NEAR(rot_y_mat, unit, 1e-5f); + EXPECT_M3_NEAR(rot_z_mat, rot_z_min_quart_pi, 1e-5f); +} + +TEST(abc_matrix, CreateRotationMatrixZ_YfromZ) { + // Input variables + float rot_x_mat[3][3]; + float rot_y_mat[3][3]; + float rot_z_mat[3][3]; + float euler[3] = {0.f, 0.f, M_PI_4}; + + // Construct expected matrices + float unit[3][3]; + float rot_y_quart_pi[3][3]; // rotation of pi/4 radians over y-axis + + unit_m3(unit); + unit_m3(rot_y_quart_pi); + rot_y_quart_pi[0][0] = M_SQRT1_2; + rot_y_quart_pi[0][2] = -M_SQRT1_2; + rot_y_quart_pi[2][0] = M_SQRT1_2; + rot_y_quart_pi[2][2] = M_SQRT1_2; + + // Run tests + create_swapped_rotation_matrix(rot_x_mat, rot_y_mat, rot_z_mat, euler, + ABC_YUP_FROM_ZUP); + + EXPECT_M3_NEAR(rot_x_mat, unit, 1e-5f); + EXPECT_M3_NEAR(rot_y_mat, rot_y_quart_pi, 1e-5f); + EXPECT_M3_NEAR(rot_z_mat, unit, 1e-5f); +} + +TEST(abc_matrix, CreateRotationMatrixXYZ_YfromZ) { + // Input variables + float rot_x_mat[3][3]; + float rot_y_mat[3][3]; + float rot_z_mat[3][3]; + // in degrees: X=10, Y=20, Z=30 + float euler[3] = {0.17453292012214f, 0.34906581044197f, 0.52359879016876f}; + + // Construct expected matrices + float rot_x_p10[3][3]; // rotation of +10 degrees over x-axis + float rot_y_p30[3][3]; // rotation of +30 degrees over y-axis + float rot_z_m20[3][3]; // rotation of -20 degrees over z-axis + + unit_m3(rot_x_p10); + rot_x_p10[1][1] = 0.9848077297210693f; + rot_x_p10[1][2] = 0.1736481785774231f; + rot_x_p10[2][1] = -0.1736481785774231f; + rot_x_p10[2][2] = 0.9848077297210693f; + + unit_m3(rot_y_p30); + rot_y_p30[0][0] = 0.8660253882408142f; + rot_y_p30[0][2] = -0.5f; + rot_y_p30[2][0] = 0.5f; + rot_y_p30[2][2] = 0.8660253882408142f; + + unit_m3(rot_z_m20); + rot_z_m20[0][0] = 0.9396926164627075f; + rot_z_m20[0][1] = -0.3420201241970062f; + rot_z_m20[1][0] = 0.3420201241970062f; + rot_z_m20[1][1] = 0.9396926164627075f; + + // Run tests + create_swapped_rotation_matrix(rot_x_mat, rot_y_mat, rot_z_mat, euler, + ABC_YUP_FROM_ZUP); + + EXPECT_M3_NEAR(rot_x_mat, rot_x_p10, 1e-5f); + EXPECT_M3_NEAR(rot_y_mat, rot_y_p30, 1e-5f); + EXPECT_M3_NEAR(rot_z_mat, rot_z_m20, 1e-5f); +} + +TEST(abc_matrix, CreateRotationMatrixXYZ_ZfromY) { + // Input variables + float rot_x_mat[3][3]; + float rot_y_mat[3][3]; + float rot_z_mat[3][3]; + // in degrees: X=10, Y=20, Z=30 + float euler[3] = {0.1745329201221466f, 0.3490658104419708f, 0.5235987901687622f}; + + // Construct expected matrices + float rot_x_p10[3][3]; // rotation of +10 degrees over x-axis + float rot_y_m30[3][3]; // rotation of -30 degrees over y-axis + float rot_z_p20[3][3]; // rotation of +20 degrees over z-axis + + unit_m3(rot_x_p10); + rot_x_p10[1][1] = 0.9848077297210693f; + rot_x_p10[1][2] = 0.1736481785774231f; + rot_x_p10[2][1] = -0.1736481785774231f; + rot_x_p10[2][2] = 0.9848077297210693f; + + unit_m3(rot_y_m30); + rot_y_m30[0][0] = 0.8660253882408142f; + rot_y_m30[0][2] = 0.5f; + rot_y_m30[2][0] = -0.5f; + rot_y_m30[2][2] = 0.8660253882408142f; + + unit_m3(rot_z_p20); + rot_z_p20[0][0] = 0.9396926164627075f; + rot_z_p20[0][1] = 0.3420201241970062f; + rot_z_p20[1][0] = -0.3420201241970062f; + rot_z_p20[1][1] = 0.9396926164627075f; + + // Run tests + create_swapped_rotation_matrix(rot_x_mat, rot_y_mat, rot_z_mat, euler, + ABC_ZUP_FROM_YUP); + + EXPECT_M3_NEAR(rot_x_mat, rot_x_p10, 1e-5f); + EXPECT_M3_NEAR(rot_y_mat, rot_y_m30, 1e-5f); + EXPECT_M3_NEAR(rot_z_mat, rot_z_p20, 1e-5f); +} + +TEST(abc_matrix, CopyM44AxisSwap_YfromZ) { + float result[4][4]; + + /* Construct an input matrix that performs a rotation like the tests + * above. This matrix was created by rotating a cube in Blender over + * (X=10, Y=20, Z=30 degrees in XYZ order) and translating over (1, 2, 3) */ + float input[4][4] = { + { 0.81379765272f, 0.4698463380336f, -0.342020124197f, 0.f}, + {-0.44096961617f, 0.8825641274452f, 0.163175910711f, 0.f}, + { 0.37852230668f, 0.0180283170193f, 0.925416588783f, 0.f}, + {1.f, 2.f, 3.f, 1.f}, + }; + + copy_m44_axis_swap(result, input, ABC_YUP_FROM_ZUP); + + /* Check the resulting rotation & translation. */ + float trans[4] = {1.f, 3.f, -2.f, 1.f}; + EXPECT_V4_NEAR(trans, result[3], 1e-5f); + + /* This matrix was created by rotating a cube in Blender over + * (X=10, Y=30, Z=-20 degrees in XZY order) and translating over (1, 3, -2) */ + float expect[4][4] = { + {0.813797652721f, -0.342020124197f, -0.469846338033f, 0.f}, + {0.378522306680f, 0.925416588783f, -0.018028317019f, 0.f}, + {0.440969616174f, -0.163175910711f, 0.882564127445f, 0.f}, + {1.f, 3.f, -2.f, 1.f}, + }; + EXPECT_M4_NEAR(expect, result, 1e-5f); +} + +TEST(abc_matrix, CopyM44AxisSwapWithScale_YfromZ) { + float result[4][4]; + + /* Construct an input matrix that performs a rotation like the tests + * above. This matrix was created by rotating a cube in Blender over + * (X=10, Y=20, Z=30 degrees in XYZ order), translating over (1, 2, 3), + * and scaling by (4, 5, 6). */ + float input[4][4] = { + { 3.25519061088f, 1.8793853521347f, -1.368080496788f, 0.f}, + {-2.20484805107f, 4.4128208160400f, 0.815879583358f, 0.f}, + { 2.27113389968f, 0.1081698983907f, 5.552499771118f, 0.f}, + {1.f, 2.f, 3.f, 1.f}, + }; + + copy_m44_axis_swap(result, input, ABC_YUP_FROM_ZUP); + + /* This matrix was created by rotating a cube in Blender over + * (X=10, Y=30, Z=-20 degrees in XZY order), translating over (1, 3, -2) + * and scaling over (4, 6, 5). */ + float expect[4][4] = { + {3.255190610885f, -1.368080496788f, -1.879385352134f, 0.f}, + {2.271133899688f, 5.552499771118f, -0.108169898390f, 0.f}, + {2.204848051071f, -0.815879583358f, 4.412820816040f, 0.f}, + {1.f, 3.f, -2.f, 1.f}, + }; + EXPECT_M4_NEAR(expect, result, 1e-5f); +} + +TEST(abc_matrix, CopyM44AxisSwap_ZfromY) { + float result[4][4]; + + /* This matrix was created by rotating a cube in Blender over + * (X=10, Y=30, Z=-20 degrees in XZY order) and translating over (1, 3, -2) */ + float input[4][4] = { + {0.813797652721f, -0.342020124197f, -0.469846338033f, 0.f}, + {0.378522306680f, 0.925416588783f, -0.018028317019f, 0.f}, + {0.440969616174f, -0.163175910711f, 0.882564127445f, 0.f}, + {1.f, 3.f, -2.f, 1.f}, + }; + + copy_m44_axis_swap(result, input, ABC_ZUP_FROM_YUP); + + /* This matrix was created by rotating a cube in Blender over + * (X=10, Y=20, Z=30 degrees in XYZ order) and translating over (1, 2, 3) */ + float expect[4][4] = { + {0.813797652721f, 0.469846338033f, -0.342020124197f, 0.f}, + {-0.44096961617f, 0.882564127445f, 0.163175910711f, 0.f}, + {0.378522306680f, 0.018028317019f, 0.925416588783f, 0.f}, + {1.f, 2.f, 3.f, 1.f}, + }; + + EXPECT_M4_NEAR(expect, result, 1e-5f); +} + +TEST(abc_matrix, CopyM44AxisSwapWithScale_ZfromY) { + float result[4][4]; + + /* This matrix was created by rotating a cube in Blender over + * (X=10, Y=30, Z=-20 degrees in XZY order), translating over (1, 3, -2) + * and scaling over (4, 6, 5). */ + float input[4][4] = { + {3.2551906108f, -1.36808049678f, -1.879385352134f, 0.f}, + {2.2711338996f, 5.55249977111f, -0.108169898390f, 0.f}, + {2.2048480510f, -0.81587958335f, 4.412820816040f, 0.f}, + {1.f, 3.f, -2.f, 1.f}, + }; + + copy_m44_axis_swap(result, input, ABC_ZUP_FROM_YUP); + + /* This matrix was created by rotating a cube in Blender over + * (X=10, Y=20, Z=30 degrees in XYZ order), translating over (1, 2, 3), + * and scaling by (4, 5, 6). */ + float expect[4][4] = { + {3.25519061088f, 1.879385352134f, -1.36808049678f, 0.f}, + {-2.2048480510f, 4.412820816040f, 0.81587958335f, 0.f}, + {2.27113389968f, 0.108169898390f, 5.55249977111f, 0.f}, + {1.f, 2.f, 3.f, 1.f}, + }; + + EXPECT_M4_NEAR(expect, result, 1e-5f); +} + +TEST(abc_matrix, CopyM44AxisSwapWithScale_gimbal_ZfromY) { + float result[4][4]; + + /* This matrix represents a rotation over (-90, -0, -0) degrees, + * and a translation over (-0, -0.1, -0). It is in Y=up. */ + float input[4][4] = { + { 1.000f, 0.000f, 0.000f, 0.000f}, + { 0.000f, 0.000f,-1.000f, 0.000f}, + { 0.000f, 1.000f, 0.000f, 0.000f}, + {-0.000f,-0.100f,-0.000f, 1.000f}, + }; + + copy_m44_axis_swap(result, input, ABC_ZUP_FROM_YUP); + + /* Since the rotation is only over the X-axis, it should not change. + * The translation does change. */ + float expect[4][4] = { + { 1.000f, 0.000f, 0.000f, 0.000f}, + { 0.000f, 0.000f,-1.000f, 0.000f}, + { 0.000f, 1.000f, 0.000f, 0.000f}, + {-0.000f, 0.000f,-0.100f, 1.000f}, + }; + + EXPECT_M4_NEAR(expect, result, 1e-5f); +} diff --git a/tests/gtests/testing/testing.h b/tests/gtests/testing/testing.h index 1594ed3926c..d5a7b076970 100644 --- a/tests/gtests/testing/testing.h +++ b/tests/gtests/testing/testing.h @@ -12,6 +12,29 @@ EXPECT_NEAR(a[2], b[2], eps); \ } (void) 0 +#define EXPECT_V4_NEAR(a, b, eps) \ +{ \ + EXPECT_NEAR(a[0], b[0], eps); \ + EXPECT_NEAR(a[1], b[1], eps); \ + EXPECT_NEAR(a[2], b[2], eps); \ + EXPECT_NEAR(a[3], b[3], eps); \ + } (void) 0 + +#define EXPECT_M3_NEAR(a, b, eps) \ +do { \ + EXPECT_V3_NEAR(a[0], b[0], eps); \ + EXPECT_V3_NEAR(a[1], b[1], eps); \ + EXPECT_V3_NEAR(a[2], b[2], eps); \ +} while(false); + +#define EXPECT_M4_NEAR(a, b, eps) \ +do { \ + EXPECT_V3_NEAR(a[0], b[0], eps); \ + EXPECT_V3_NEAR(a[1], b[1], eps); \ + EXPECT_V3_NEAR(a[2], b[2], eps); \ + EXPECT_V4_NEAR(a[3], b[3], eps); \ +} while(false); + #define EXPECT_MATRIX_NEAR(a, b, tolerance) \ do { \ bool dims_match = (a.rows() == b.rows()) && (a.cols() == b.cols()); \ |