diff options
Diffstat (limited to 'source/blender/io')
153 files changed, 33820 insertions, 0 deletions
diff --git a/source/blender/io/CMakeLists.txt b/source/blender/io/CMakeLists.txt new file mode 100644 index 00000000000..bc2f8d628e2 --- /dev/null +++ b/source/blender/io/CMakeLists.txt @@ -0,0 +1,35 @@ +# ***** 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) 2020, Blender Foundation +# All rights reserved. +# ***** END GPL LICENSE BLOCK ***** + +if(WITH_ALEMBIC) + add_subdirectory(alembic) +endif() + +if(WITH_CODEC_AVI) + add_subdirectory(avi) +endif() + +if(WITH_OPENCOLLADA) + add_subdirectory(collada) +endif() + +if(WITH_USD) + add_subdirectory(usd) +endif() diff --git a/source/blender/io/alembic/ABC_alembic.h b/source/blender/io/alembic/ABC_alembic.h new file mode 100644 index 00000000000..878dbfc2a53 --- /dev/null +++ b/source/blender/io/alembic/ABC_alembic.h @@ -0,0 +1,141 @@ +/* + * 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 + */ + +#ifndef __ABC_ALEMBIC_H__ +#define __ABC_ALEMBIC_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +struct CacheReader; +struct ListBase; +struct Main; +struct Mesh; +struct Object; +struct Scene; +struct bContext; + +typedef struct AbcArchiveHandle AbcArchiveHandle; + +enum { + ABC_ARCHIVE_OGAWA = 0, + ABC_ARCHIVE_HDF5 = 1, +}; + +int ABC_get_version(void); + +struct AlembicExportParams { + double frame_start; + double frame_end; + + unsigned int frame_samples_xform; + unsigned int frame_samples_shape; + + double shutter_open; + double shutter_close; + + bool selected_only; + bool uvs; + bool normals; + bool vcolors; + bool apply_subdiv; + bool curves_as_mesh; + bool flatten_hierarchy; + bool visible_objects_only; + bool renderable_only; + bool face_sets; + bool use_subdiv_schema; + bool packuv; + bool triangulate; + bool export_hair; + bool export_particles; + + unsigned int compression_type : 1; + + /* See MOD_TRIANGULATE_NGON_xxx and MOD_TRIANGULATE_QUAD_xxx + * in DNA_modifier_types.h */ + int quad_method; + int ngon_method; + + float global_scale; +}; + +/* The ABC_export and ABC_import functions both take a as_background_job + * parameter, and return a boolean. + * + * When as_background_job=true, returns false immediately after scheduling + * a background job. + * + * When as_background_job=false, performs the export synchronously, and returns + * true when the export was ok, and false if there were any errors. + */ + +bool ABC_export(struct Scene *scene, + struct bContext *C, + const char *filepath, + const struct AlembicExportParams *params, + bool as_background_job); + +bool ABC_import(struct bContext *C, + const char *filepath, + float scale, + bool is_sequence, + bool set_frame_range, + int sequence_len, + int offset, + bool validate_meshes, + bool as_background_job); + +AbcArchiveHandle *ABC_create_handle(struct Main *bmain, + const char *filename, + struct ListBase *object_paths); + +void ABC_free_handle(AbcArchiveHandle *handle); + +void ABC_get_transform(struct CacheReader *reader, float r_mat[4][4], float time, float scale); + +/* Either modifies current_mesh in-place or constructs a new mesh. */ +struct Mesh *ABC_read_mesh(struct CacheReader *reader, + struct Object *ob, + struct Mesh *current_mesh, + const float time, + const char **err_str, + int flags); + +bool ABC_mesh_topology_changed(struct CacheReader *reader, + struct Object *ob, + struct Mesh *existing_mesh, + const float time, + const char **err_str); + +void CacheReader_incref(struct CacheReader *reader); +void CacheReader_free(struct CacheReader *reader); + +struct CacheReader *CacheReader_open_alembic_object(struct AbcArchiveHandle *handle, + struct CacheReader *reader, + struct Object *object, + const char *object_path); + +#ifdef __cplusplus +} +#endif + +#endif /* __ABC_ALEMBIC_H__ */ diff --git a/source/blender/io/alembic/CMakeLists.txt b/source/blender/io/alembic/CMakeLists.txt new file mode 100644 index 00000000000..cbcdfaf4b77 --- /dev/null +++ b/source/blender/io/alembic/CMakeLists.txt @@ -0,0 +1,110 @@ +# ***** 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) 2006, Blender Foundation +# All rights reserved. +# ***** END GPL LICENSE BLOCK ***** + +set(INC + . + ../../blenkernel + ../../blenlib + ../../blenloader + ../../bmesh + ../../depsgraph + ../../editors/include + ../../makesdna + ../../makesrna + ../../windowmanager + ../../../../intern/guardedalloc + ../../../../intern/utfconv +) + +set(INC_SYS + ${ALEMBIC_INCLUDE_DIRS} + ${BOOST_INCLUDE_DIR} + ${HDF5_INCLUDE_DIRS} + ${OPENEXR_INCLUDE_DIRS} +) + +set(SRC + intern/abc_customdata.cc + intern/abc_exporter.cc + intern/abc_reader_archive.cc + intern/abc_reader_camera.cc + intern/abc_reader_curves.cc + intern/abc_reader_mesh.cc + intern/abc_reader_nurbs.cc + intern/abc_reader_object.cc + intern/abc_reader_points.cc + intern/abc_reader_transform.cc + intern/abc_util.cc + intern/abc_writer_archive.cc + intern/abc_writer_camera.cc + intern/abc_writer_curves.cc + intern/abc_writer_hair.cc + intern/abc_writer_mball.cc + intern/abc_writer_mesh.cc + intern/abc_writer_nurbs.cc + intern/abc_writer_object.cc + intern/abc_writer_points.cc + intern/abc_writer_transform.cc + intern/alembic_capi.cc + + ABC_alembic.h + intern/abc_customdata.h + intern/abc_exporter.h + intern/abc_reader_archive.h + intern/abc_reader_camera.h + intern/abc_reader_curves.h + intern/abc_reader_mesh.h + intern/abc_reader_nurbs.h + intern/abc_reader_object.h + intern/abc_reader_points.h + intern/abc_reader_transform.h + intern/abc_util.h + intern/abc_writer_archive.h + intern/abc_writer_camera.h + intern/abc_writer_curves.h + intern/abc_writer_hair.h + intern/abc_writer_mball.h + intern/abc_writer_mesh.h + intern/abc_writer_nurbs.h + intern/abc_writer_object.h + intern/abc_writer_points.h + intern/abc_writer_transform.h +) + +set(LIB + bf_blenkernel + bf_blenlib + + ${ALEMBIC_LIBRARIES} + ${OPENEXR_LIBRARIES} +) + +if(WITH_ALEMBIC_HDF5) + add_definitions(-DWITH_ALEMBIC_HDF5) + list(APPEND LIB + ${HDF5_LIBRARIES} + ) +endif() + +list(APPEND LIB + ${BOOST_LIBRARIES} +) + +blender_add_lib(bf_alembic "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/io/alembic/intern/abc_customdata.cc b/source/blender/io/alembic/intern/abc_customdata.cc new file mode 100644 index 00000000000..c5f60ac3e29 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_customdata.cc @@ -0,0 +1,484 @@ +/* + * 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. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_customdata.h" + +#include <Alembic/AbcGeom/All.h> +#include <algorithm> +#include <unordered_map> + +extern "C" { +#include "DNA_customdata_types.h" +#include "DNA_meshdata_types.h" + +#include "BLI_math_base.h" +#include "BLI_utildefines.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::kFacevaryingScope; +using Alembic::AbcGeom::kVertexScope; + +using Alembic::Abc::C4fArraySample; +using Alembic::Abc::UInt32ArraySample; +using Alembic::Abc::V2fArraySample; + +using Alembic::AbcGeom::OC4fGeomParam; +using Alembic::AbcGeom::OV2fGeomParam; + +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; + MLoop *mloop = config.mloop; + + if (!config.pack_uvs) { + int cnt = 0; + uvidx.resize(config.totloop); + uvs.resize(config.totloop); + + /* Iterate in reverse order to match exported polygons. */ + for (int i = 0; i < num_poly; i++) { + MPoly ¤t_poly = polygons[i]; + MLoopUV *loopuv = mloopuv_array + current_poly.loopstart + current_poly.totloop; + + for (int j = 0; j < current_poly.totloop; j++, cnt++) { + loopuv--; + + uvidx[cnt] = cnt; + uvs[cnt][0] = loopuv->uv[0]; + uvs[cnt][1] = loopuv->uv[1]; + } + } + } + else { + /* Mapping for indexed UVs, deduplicating UV coordinates at vertices. */ + std::vector<std::vector<uint32_t>> idx_map(config.totvert); + int idx_count = 0; + + for (int i = 0; i < num_poly; i++) { + MPoly ¤t_poly = polygons[i]; + MLoop *looppoly = mloop + current_poly.loopstart + current_poly.totloop; + MLoopUV *loopuv = mloopuv_array + current_poly.loopstart + current_poly.totloop; + + for (int j = 0; j < current_poly.totloop; j++) { + looppoly--; + loopuv--; + + Imath::V2f uv(loopuv->uv[0], loopuv->uv[1]); + bool found_same = false; + + /* Find UV already in uvs array. */ + for (uint32_t uv_idx : idx_map[looppoly->v]) { + if (uvs[uv_idx] == uv) { + found_same = true; + uvidx.push_back(uv_idx); + break; + } + } + + /* UV doesn't exists for this vertex, add it. */ + if (!found_same) { + uint32_t uv_idx = idx_count++; + idx_map[looppoly->v].push_back(uv_idx); + uvidx.push_back(uv_idx); + uvs.push_back(uv); + } + } + } + } +} + +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 behavior) 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; + std::vector<uint32_t> indices; + + buffer.reserve(config.totvert); + indices.reserve(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.push_back(col); + indices.push_back(buffer.size() - 1); + } + } + + OC4fGeomParam param(prop, name, true, kFacevaryingScope, 1); + + OC4fGeomParam::Sample sample(C4fArraySample(&buffer.front(), buffer.size()), + UInt32ArraySample(&indices.front(), indices.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_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, rev_loop_index; + + for (int i = 0; i < config.totpoly; i++) { + MPoly &poly = mpolys[i]; + unsigned int rev_loop_offset = poly.loopstart + poly.totloop - 1; + + for (int f = 0; f < poly.totloop; f++) { + loop_index = poly.loopstart + f; + rev_loop_index = rev_loop_offset - f; + uv_index = (*indices)[loop_index]; + const Imath::V2f &uv = (*uvs)[uv_index]; + + MLoopUV &loopuv = mloopuvs[rev_loop_index]; + loopuv.uv[0] = uv[0]; + loopuv.uv[1] = uv[1]; + } + } +} + +static size_t mcols_out_of_bounds_check(const size_t color_index, + const size_t array_size, + const std::string &iobject_full_name, + const PropertyHeader &prop_header, + bool &r_is_out_of_bounds, + bool &r_bounds_warning_given) +{ + if (color_index < array_size) { + return color_index; + } + + if (!r_bounds_warning_given) { + std::cerr << "Alembic: color index out of bounds " + "reading face colors for object " + << iobject_full_name << ", property " << prop_header.getName() << std::endl; + r_bounds_warning_given = true; + } + r_is_out_of_bounds = true; + return 0; +} + +static void read_custom_data_mcols(const std::string &iobject_full_name, + const ICompoundProperty &arbGeomParams, + const PropertyHeader &prop_header, + const CDStreamConfig &config, + const Alembic::Abc::ISampleSelector &iss) +{ + C3fArraySamplePtr c3f_ptr = C3fArraySamplePtr(); + C4fArraySamplePtr c4f_ptr = C4fArraySamplePtr(); + Alembic::Abc::UInt32ArraySamplePtr indices; + bool use_c3f_ptr; + bool is_facevarying; + + /* Find the correct interpretation of the data */ + if (IC3fGeomParam::matches(prop_header)) { + IC3fGeomParam color_param(arbGeomParams, prop_header.getName()); + IC3fGeomParam::Sample sample; + BLI_assert(!strcmp("rgb", color_param.getInterpretation())); + + color_param.getIndexed(sample, iss); + is_facevarying = sample.getScope() == kFacevaryingScope && + config.totloop == sample.getIndices()->size(); + + c3f_ptr = sample.getVals(); + indices = sample.getIndices(); + use_c3f_ptr = true; + } + else if (IC4fGeomParam::matches(prop_header)) { + IC4fGeomParam color_param(arbGeomParams, prop_header.getName()); + IC4fGeomParam::Sample sample; + BLI_assert(!strcmp("rgba", color_param.getInterpretation())); + + color_param.getIndexed(sample, iss); + is_facevarying = sample.getScope() == kFacevaryingScope && + config.totloop == sample.getIndices()->size(); + + c4f_ptr = sample.getVals(); + indices = sample.getIndices(); + use_c3f_ptr = false; + } + else { + /* this won't happen due to the checks in read_custom_data() */ + return; + } + BLI_assert(c3f_ptr || c4f_ptr); + + /* Read the vertex colors */ + void *cd_data = config.add_customdata_cb( + config.mesh, prop_header.getName().c_str(), CD_MLOOPCOL); + MCol *cfaces = static_cast<MCol *>(cd_data); + MPoly *mpolys = config.mpoly; + MLoop *mloops = config.mloop; + + size_t face_index = 0; + size_t color_index; + bool bounds_warning_given = false; + + /* The colors can go through two layers of indexing. Often the 'indices' + * array doesn't do anything (i.e. indices[n] = n), but when it does, it's + * important. Blender 2.79 writes indices incorrectly (see T53745), which + * is why we have to check for indices->size() > 0 */ + bool use_dual_indexing = is_facevarying && indices->size() > 0; + + for (int i = 0; i < config.totpoly; i++) { + MPoly *poly = &mpolys[i]; + MCol *cface = &cfaces[poly->loopstart + poly->totloop]; + MLoop *mloop = &mloops[poly->loopstart + poly->totloop]; + + for (int j = 0; j < poly->totloop; j++, face_index++) { + cface--; + mloop--; + + color_index = is_facevarying ? face_index : mloop->v; + if (use_dual_indexing) { + color_index = (*indices)[color_index]; + } + if (use_c3f_ptr) { + bool is_mcols_out_of_bounds = false; + color_index = mcols_out_of_bounds_check(color_index, + c3f_ptr->size(), + iobject_full_name, + prop_header, + is_mcols_out_of_bounds, + bounds_warning_given); + if (is_mcols_out_of_bounds) { + continue; + } + const Imath::C3f &color = (*c3f_ptr)[color_index]; + cface->a = unit_float_to_uchar_clamp(color[0]); + cface->r = unit_float_to_uchar_clamp(color[1]); + cface->g = unit_float_to_uchar_clamp(color[2]); + cface->b = 255; + } + else { + bool is_mcols_out_of_bounds = false; + color_index = mcols_out_of_bounds_check(color_index, + c4f_ptr->size(), + iobject_full_name, + prop_header, + is_mcols_out_of_bounds, + bounds_warning_given); + if (is_mcols_out_of_bounds) { + continue; + } + const Imath::C4f &color = (*c4f_ptr)[color_index]; + cface->a = unit_float_to_uchar_clamp(color[0]); + cface->r = unit_float_to_uchar_clamp(color[1]); + cface->g = unit_float_to_uchar_clamp(color[2]); + cface->b = unit_float_to_uchar_clamp(color[3]); + } + } + } +} + +static void read_custom_data_uvs(const ICompoundProperty &prop, + const PropertyHeader &prop_header, + const CDStreamConfig &config, + const Alembic::Abc::ISampleSelector &iss) +{ + IV2fGeomParam uv_param(prop, prop_header.getName()); + + if (!uv_param.isIndexed()) { + return; + } + + IV2fGeomParam::Sample sample; + uv_param.getIndexed(sample, iss); + + if (uv_param.getScope() != kFacevaryingScope) { + return; + } + + void *cd_data = config.add_customdata_cb(config.mesh, prop_header.getName().c_str(), CD_MLOOPUV); + + read_uvs(config, cd_data, sample.getVals(), sample.getIndices()); +} + +void read_custom_data(const std::string &iobject_full_name, + 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_uvs(prop, prop_header, config, iss); + 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_mcols(iobject_full_name, prop, prop_header, config, iss); + continue; + } + } +} diff --git a/source/blender/io/alembic/intern/abc_customdata.h b/source/blender/io/alembic/intern/abc_customdata.h new file mode 100644 index 00000000000..6107e230627 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_customdata.h @@ -0,0 +1,104 @@ +/* + * 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. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_CUSTOMDATA_H__ +#define __ABC_CUSTOMDATA_H__ + +#include <Alembic/Abc/All.h> +#include <Alembic/AbcGeom/All.h> + +struct CustomData; +struct MLoop; +struct MLoopUV; +struct MPoly; +struct MVert; +struct Mesh; + +using Alembic::Abc::ICompoundProperty; +using Alembic::Abc::OCompoundProperty; + +struct UVSample { + std::vector<Imath::V2f> uvs; + std::vector<uint32_t> indices; +}; + +struct CDStreamConfig { + MLoop *mloop; + int totloop; + + MPoly *mpoly; + int totpoly; + + MVert *mvert; + int totvert; + + MLoopUV *mloopuv; + + CustomData *loopdata; + + bool pack_uvs; + + /* TODO(kevin): might need a better way to handle adding and/or updating + * custom data such that it updates the custom data holder and its pointers properly. */ + Mesh *mesh; + void *(*add_customdata_cb)(Mesh *mesh, const char *name, int data_type); + + float weight; + float time; + Alembic::AbcGeom::index_t index; + Alembic::AbcGeom::index_t ceil_index; + + CDStreamConfig() + : mloop(NULL), + totloop(0), + mpoly(NULL), + totpoly(0), + totvert(0), + pack_uvs(false), + mesh(NULL), + add_customdata_cb(NULL), + weight(0.0f), + time(0.0f), + index(0), + ceil_index(0) + { + } +}; + +/* Get the UVs for the main UV property on a OSchema. + * Returns the name of the UV layer. + * + * For now the active layer is used, maybe needs a better way to choose this. */ +const char *get_uv_sample(UVSample &sample, const CDStreamConfig &config, CustomData *data); + +void write_custom_data(const OCompoundProperty &prop, + const CDStreamConfig &config, + CustomData *data, + int data_type); + +void read_custom_data(const std::string &iobject_full_name, + const ICompoundProperty &prop, + const CDStreamConfig &config, + const Alembic::Abc::ISampleSelector &iss); + +#endif /* __ABC_CUSTOMDATA_H__ */ diff --git a/source/blender/io/alembic/intern/abc_exporter.cc b/source/blender/io/alembic/intern/abc_exporter.cc new file mode 100644 index 00000000000..a58b0a29e5e --- /dev/null +++ b/source/blender/io/alembic/intern/abc_exporter.cc @@ -0,0 +1,677 @@ +/* + * 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_exporter.h" + +#include <cmath> + +#include "abc_writer_archive.h" +#include "abc_writer_camera.h" +#include "abc_writer_curves.h" +#include "abc_writer_hair.h" +#include "abc_writer_mball.h" +#include "abc_writer_mesh.h" +#include "abc_writer_nurbs.h" +#include "abc_writer_points.h" +#include "abc_writer_transform.h" +#include "abc_util.h" + +extern "C" { +#include "DNA_camera_types.h" +#include "DNA_curve_types.h" +#include "DNA_meta_types.h" +#include "DNA_mesh_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_space_types.h" /* for FILE_MAX */ +#include "DNA_fluid_types.h" + +#include "BLI_string.h" + +#ifdef WIN32 +/* needed for MSCV because of snprintf from BLI_string */ +# include "BLI_winstuff.h" +#endif + +#include "BKE_anim.h" +#include "BKE_global.h" +#include "BKE_idprop.h" +#include "BKE_layer.h" +#include "BKE_main.h" +#include "BKE_mball.h" +#include "BKE_modifier.h" +#include "BKE_particle.h" +#include "BKE_scene.h" + +#include "DEG_depsgraph_query.h" +} + +using Alembic::Abc::OBox3dProperty; +using Alembic::Abc::TimeSamplingPtr; + +/* ************************************************************************** */ + +ExportSettings::ExportSettings() + : scene(NULL), + view_layer(NULL), + depsgraph(NULL), + logger(), + selected_only(false), + visible_objects_only(false), + renderable_only(false), + frame_start(1), + frame_end(1), + frame_samples_xform(1), + frame_samples_shape(1), + shutter_open(0.0), + shutter_close(1.0), + global_scale(1.0f), + flatten_hierarchy(false), + export_normals(false), + export_uvs(false), + export_vcols(false), + export_face_sets(false), + export_vweigths(false), + export_hair(true), + export_particles(true), + apply_subdiv(false), + use_subdiv_schema(false), + export_child_hairs(true), + export_ogawa(true), + pack_uv(false), + triangulate(false), + quad_method(0), + ngon_method(0) +{ +} + +static bool object_is_smoke_sim(Object *ob) +{ + ModifierData *md = modifiers_findByType(ob, eModifierType_Fluid); + + if (md) { + FluidModifierData *smd = reinterpret_cast<FluidModifierData *>(md); + return (smd->type == MOD_FLUID_TYPE_DOMAIN && smd->domain && + smd->domain->type == FLUID_DOMAIN_TYPE_GAS); + } + + return false; +} + +static bool object_type_is_exportable(Scene *scene, Object *ob) +{ + switch (ob->type) { + case OB_MESH: + if (object_is_smoke_sim(ob)) { + return false; + } + + return true; + case OB_EMPTY: + case OB_CURVE: + case OB_SURF: + case OB_CAMERA: + return true; + case OB_MBALL: + return AbcMBallWriter::isBasisBall(scene, ob); + default: + return false; + } +} + +/** + * Returns whether this object should be exported into the Alembic file. + * + * \param settings: export settings, used for options like 'selected only'. + * \param ob: the object's base in question. + * \param is_duplicated: Normally false; true when the object is instanced + * into the scene by a dupli-object (e.g. part of a dupligroup). + * This ignores selection and layer visibility, + * and assumes that the dupli-object itself (e.g. the group-instantiating empty) is exported. + */ +static bool export_object(const ExportSettings *const settings, + const Base *const base, + bool is_duplicated) +{ + if (!is_duplicated) { + View3D *v3d = NULL; + + /* These two tests only make sense when the object isn't being instanced + * into the scene. When it is, its exportability is determined by + * its dupli-object and the DupliObject::no_draw property. */ + if (settings->selected_only && !BASE_SELECTED(v3d, base)) { + return false; + } + // FIXME Sybren: handle these cleanly (maybe just remove code), + // now using active scene layer instead. + if (settings->visible_objects_only && !BASE_VISIBLE(v3d, base)) { + return false; + } + } + + Object *ob_eval = DEG_get_evaluated_object(settings->depsgraph, base->object); + if ((ob_eval->id.tag & LIB_TAG_COPIED_ON_WRITE) == 0) { + /* XXX fix after 2.80: the object was not part of the depsgraph, and thus we cannot get the + * evaluated copy to export. This will be handled more elegantly in the new + * AbstractHierarchyIterator that Sybren is working on. This condition is temporary, and avoids + * a BLI_assert() failure getting the evaluated mesh of this object. */ + return false; + } + + // if (settings->renderable_only && (ob->restrictflag & OB_RESTRICT_RENDER)) { + // return false; + // } + + return true; +} + +/* ************************************************************************** */ + +AbcExporter::AbcExporter(Main *bmain, const char *filename, ExportSettings &settings) + : m_bmain(bmain), + m_settings(settings), + m_filename(filename), + m_trans_sampling_index(0), + m_shape_sampling_index(0), + m_writer(NULL) +{ +} + +AbcExporter::~AbcExporter() +{ + /* 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]; + } + + delete m_writer; +} + +void AbcExporter::getShutterSamples(unsigned int nr_of_samples, + bool time_relative, + std::vector<double> &samples) +{ + Scene *scene = m_settings.scene; /* for use in the FPS macro */ + samples.clear(); + + unsigned int frame_offset = time_relative ? m_settings.frame_start : 0; + double time_factor = time_relative ? FPS : 1.0; + double shutter_open = m_settings.shutter_open; + double shutter_close = m_settings.shutter_close; + double time_inc = (shutter_close - shutter_open) / nr_of_samples; + + /* sample between shutter open & close */ + for (int sample = 0; sample < nr_of_samples; sample++) { + double sample_time = shutter_open + time_inc * sample; + double time = (frame_offset + sample_time) / time_factor; + + samples.push_back(time); + } +} + +Alembic::Abc::TimeSamplingPtr AbcExporter::createTimeSampling(double step) +{ + std::vector<double> samples; + + if (m_settings.frame_start == m_settings.frame_end) { + return TimeSamplingPtr(new Alembic::Abc::TimeSampling()); + } + + getShutterSamples(step, true, samples); + + /* TODO(Sybren): shouldn't we use the FPS macro here? */ + Alembic::Abc::TimeSamplingType ts(static_cast<uint32_t>(samples.size()), + 1.0 / m_settings.scene->r.frs_sec); + + return TimeSamplingPtr(new Alembic::Abc::TimeSampling(ts, samples)); +} + +void AbcExporter::getFrameSet(unsigned int nr_of_samples, std::set<double> &frames) +{ + frames.clear(); + + std::vector<double> shutter_samples; + + getShutterSamples(nr_of_samples, false, shutter_samples); + + for (double frame = m_settings.frame_start; frame <= m_settings.frame_end; frame += 1.0) { + for (size_t j = 0; j < nr_of_samples; j++) { + frames.insert(frame + shutter_samples[j]); + } + } +} + +void AbcExporter::operator()(short *do_update, float *progress, bool *was_canceled) +{ + std::string abc_scene_name; + + if (m_bmain->name[0] != '\0') { + char scene_file_name[FILE_MAX]; + BLI_strncpy(scene_file_name, m_bmain->name, FILE_MAX); + abc_scene_name = scene_file_name; + } + else { + abc_scene_name = "untitled"; + } + + m_writer = new ArchiveWriter( + m_filename, abc_scene_name, m_settings.scene, m_settings.export_ogawa); + + /* Create time samplings for transforms and shapes. */ + + TimeSamplingPtr trans_time = createTimeSampling(m_settings.frame_samples_xform); + + m_trans_sampling_index = m_writer->archive().addTimeSampling(*trans_time); + + TimeSamplingPtr shape_time; + + if ((m_settings.frame_samples_shape == m_settings.frame_samples_xform) || + (m_settings.frame_start == m_settings.frame_end)) { + shape_time = trans_time; + m_shape_sampling_index = m_trans_sampling_index; + } + else { + shape_time = createTimeSampling(m_settings.frame_samples_shape); + m_shape_sampling_index = m_writer->archive().addTimeSampling(*shape_time); + } + + OBox3dProperty archive_bounds_prop = Alembic::AbcGeom::CreateOArchiveBounds( + m_writer->archive(), m_trans_sampling_index); + + createTransformWritersHierarchy(); + createShapeWriters(); + + /* Make a list of frames to export. */ + + std::set<double> xform_frames; + getFrameSet(m_settings.frame_samples_xform, xform_frames); + + std::set<double> shape_frames; + getFrameSet(m_settings.frame_samples_shape, shape_frames); + + /* Merge all frames needed. */ + std::set<double> frames(xform_frames); + frames.insert(shape_frames.begin(), shape_frames.end()); + + /* Export all frames. */ + + std::set<double>::const_iterator begin = frames.begin(); + std::set<double>::const_iterator end = frames.end(); + + const float size = static_cast<float>(frames.size()); + size_t i = 0; + + for (; begin != end; ++begin) { + *progress = (++i / size); + *do_update = 1; + + if (G.is_break) { + *was_canceled = true; + break; + } + + const double frame = *begin; + + /* 'frame' is offset by start frame, so need to cancel the offset. */ + setCurrentFrame(m_bmain, frame); + + if (shape_frames.count(frame) != 0) { + for (int i = 0, e = m_shapes.size(); i != e; i++) { + m_shapes[i]->write(); + } + } + + if (xform_frames.count(frame) == 0) { + continue; + } + + m_xforms_type::iterator xit, xe; + for (xit = m_xforms.begin(), xe = m_xforms.end(); xit != xe; ++xit) { + xit->second->write(); + } + + /* Save the archive 's bounding box. */ + Imath::Box3d bounds; + + for (xit = m_xforms.begin(), xe = m_xforms.end(); xit != xe; ++xit) { + Imath::Box3d box = xit->second->bounds(); + bounds.extendBy(box); + } + + archive_bounds_prop.set(bounds); + } +} + +void AbcExporter::createTransformWritersHierarchy() +{ + for (Base *base = static_cast<Base *>(m_settings.view_layer->object_bases.first); base; + base = base->next) { + Object *ob = base->object; + + if (export_object(&m_settings, base, false)) { + switch (ob->type) { + case OB_LAMP: + case OB_LATTICE: + case OB_SPEAKER: + /* We do not export transforms for objects of these classes. */ + break; + default: + exploreTransform(base, ob, ob->parent, NULL); + } + } + } +} + +void AbcExporter::exploreTransform(Base *base, + Object *object, + Object *parent, + Object *dupliObParent) +{ + /* If an object isn't exported itself, its duplilist shouldn't be + * exported either. */ + if (!export_object(&m_settings, base, dupliObParent != NULL)) { + return; + } + + Object *ob = DEG_get_evaluated_object(m_settings.depsgraph, object); + if (object_type_is_exportable(m_settings.scene, ob)) { + createTransformWriter(ob, parent, dupliObParent); + } + + ListBase *lb = object_duplilist(m_settings.depsgraph, m_settings.scene, ob); + + if (lb) { + DupliObject *link = static_cast<DupliObject *>(lb->first); + Object *dupli_ob = NULL; + Object *dupli_parent = NULL; + + for (; link; link = link->next) { + /* This skips things like custom bone shapes. */ + if (m_settings.renderable_only && link->no_draw) { + continue; + } + + if (link->type == OB_DUPLICOLLECTION) { + dupli_ob = link->ob; + dupli_parent = (dupli_ob->parent) ? dupli_ob->parent : ob; + + exploreTransform(base, dupli_ob, dupli_parent, ob); + } + } + + free_object_duplilist(lb); + } +} + +AbcTransformWriter *AbcExporter::createTransformWriter(Object *ob, + Object *parent, + Object *dupliObParent) +{ + /* An object should not be its own parent, or we'll get infinite loops. */ + BLI_assert(ob != parent); + BLI_assert(ob != dupliObParent); + + std::string name; + if (m_settings.flatten_hierarchy) { + name = get_id_name(ob); + } + else { + name = get_object_dag_path_name(ob, dupliObParent); + } + + /* check if we have already created a transform writer for this object */ + AbcTransformWriter *my_writer = getXForm(name); + if (my_writer != NULL) { + return my_writer; + } + + AbcTransformWriter *parent_writer = NULL; + Alembic::Abc::OObject alembic_parent; + + if (m_settings.flatten_hierarchy || parent == NULL) { + /* Parentless objects still have the "top object" as parent + * in Alembic. */ + alembic_parent = m_writer->archive().getTop(); + } + else { + /* 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) { + if (parent == dupliObParent) { + parent_writer = createTransformWriter(parent, parent->parent, NULL); + } + else { + parent_writer = createTransformWriter(parent, parent->parent, dupliObParent); + } + } + else if (parent == dupliObParent) { + if (dupliObParent->parent == NULL) { + parent_writer = createTransformWriter(parent, NULL, NULL); + } + else { + parent_writer = createTransformWriter( + parent, dupliObParent->parent, dupliObParent->parent); + } + } + else { + parent_writer = createTransformWriter(parent, dupliObParent, dupliObParent); + } + + BLI_assert(parent_writer); + alembic_parent = parent_writer->alembicXform(); + } + + my_writer = new AbcTransformWriter( + ob, alembic_parent, parent_writer, m_trans_sampling_index, m_settings); + + /* When flattening, the matrix of the dupliobject has to be added. */ + if (m_settings.flatten_hierarchy && dupliObParent) { + my_writer->m_proxy_from = dupliObParent; + } + + m_xforms[name] = my_writer; + return my_writer; +} + +void AbcExporter::createShapeWriters() +{ + for (Base *base = static_cast<Base *>(m_settings.view_layer->object_bases.first); base; + base = base->next) { + exploreObject(base, base->object, NULL); + } +} + +void AbcExporter::exploreObject(Base *base, Object *object, Object *dupliObParent) +{ + /* If an object isn't exported itself, its duplilist shouldn't be + * exported either. */ + if (!export_object(&m_settings, base, dupliObParent != NULL)) { + return; + } + + Object *ob = DEG_get_evaluated_object(m_settings.depsgraph, object); + createShapeWriter(ob, dupliObParent); + + ListBase *lb = object_duplilist(m_settings.depsgraph, m_settings.scene, ob); + + if (lb) { + DupliObject *link = static_cast<DupliObject *>(lb->first); + + for (; link; link = link->next) { + /* This skips things like custom bone shapes. */ + if (m_settings.renderable_only && link->no_draw) { + continue; + } + if (link->type == OB_DUPLICOLLECTION) { + exploreObject(base, link->ob, ob); + } + } + + free_object_duplilist(lb); + } +} + +void AbcExporter::createParticleSystemsWriters(Object *ob, AbcTransformWriter *xform) +{ + if (!m_settings.export_hair && !m_settings.export_particles) { + return; + } + + ParticleSystem *psys = static_cast<ParticleSystem *>(ob->particlesystem.first); + + for (; psys; psys = psys->next) { + if (!psys_check_enabled(ob, psys, G.is_rendering) || !psys->part) { + continue; + } + + if (m_settings.export_hair && psys->part->type == PART_HAIR) { + m_settings.export_child_hairs = true; + m_shapes.push_back(new AbcHairWriter(ob, xform, m_shape_sampling_index, m_settings, psys)); + } + else if (m_settings.export_particles && + (psys->part->type == PART_EMITTER || psys->part->type == PART_FLUID_FLIP || + psys->part->type == PART_FLUID_SPRAY || psys->part->type == PART_FLUID_BUBBLE || + psys->part->type == PART_FLUID_FOAM || psys->part->type == PART_FLUID_TRACER || + psys->part->type == PART_FLUID_SPRAYFOAM || + psys->part->type == PART_FLUID_SPRAYBUBBLE || + psys->part->type == PART_FLUID_FOAMBUBBLE || + psys->part->type == PART_FLUID_SPRAYFOAMBUBBLE)) { + m_shapes.push_back(new AbcPointsWriter(ob, xform, m_shape_sampling_index, m_settings, psys)); + } + } +} + +void AbcExporter::createShapeWriter(Object *ob, Object *dupliObParent) +{ + if (!object_type_is_exportable(m_settings.scene, ob)) { + return; + } + + std::string name; + + if (m_settings.flatten_hierarchy) { + name = get_id_name(ob); + } + else { + name = get_object_dag_path_name(ob, dupliObParent); + } + + AbcTransformWriter *xform = getXForm(name); + + if (!xform) { + ABC_LOG(m_settings.logger) << __func__ << ": xform " << name << " is NULL\n"; + return; + } + + createParticleSystemsWriters(ob, xform); + + switch (ob->type) { + case OB_MESH: { + Mesh *me = static_cast<Mesh *>(ob->data); + + if (!me) { + return; + } + + m_shapes.push_back(new AbcMeshWriter(ob, xform, m_shape_sampling_index, m_settings)); + break; + } + case OB_SURF: { + Curve *cu = static_cast<Curve *>(ob->data); + + if (!cu) { + return; + } + + AbcObjectWriter *writer; + if (m_settings.curves_as_mesh) { + writer = new AbcCurveMeshWriter(ob, xform, m_shape_sampling_index, m_settings); + } + else { + writer = new AbcNurbsWriter(ob, xform, m_shape_sampling_index, m_settings); + } + m_shapes.push_back(writer); + break; + } + case OB_CURVE: { + Curve *cu = static_cast<Curve *>(ob->data); + + if (!cu) { + return; + } + + AbcObjectWriter *writer; + if (m_settings.curves_as_mesh) { + writer = new AbcCurveMeshWriter(ob, xform, m_shape_sampling_index, m_settings); + } + else { + writer = new AbcCurveWriter(ob, xform, m_shape_sampling_index, m_settings); + } + m_shapes.push_back(writer); + break; + } + case OB_CAMERA: { + Camera *cam = static_cast<Camera *>(ob->data); + + if (cam->type == CAM_PERSP) { + m_shapes.push_back(new AbcCameraWriter(ob, xform, m_shape_sampling_index, m_settings)); + } + + break; + } + case OB_MBALL: { + MetaBall *mball = static_cast<MetaBall *>(ob->data); + if (!mball) { + return; + } + + m_shapes.push_back( + new AbcMBallWriter(m_bmain, ob, xform, m_shape_sampling_index, m_settings)); + break; + } + } +} + +AbcTransformWriter *AbcExporter::getXForm(const std::string &name) +{ + std::map<std::string, AbcTransformWriter *>::iterator it = m_xforms.find(name); + + if (it == m_xforms.end()) { + return NULL; + } + + return it->second; +} + +void AbcExporter::setCurrentFrame(Main *bmain, double t) +{ + m_settings.scene->r.cfra = static_cast<int>(t); + m_settings.scene->r.subframe = static_cast<float>(t) - m_settings.scene->r.cfra; + BKE_scene_graph_update_for_newframe(m_settings.depsgraph, bmain); +} diff --git a/source/blender/io/alembic/intern/abc_exporter.h b/source/blender/io/alembic/intern/abc_exporter.h new file mode 100644 index 00000000000..398004d2ec5 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_exporter.h @@ -0,0 +1,128 @@ +/* + * 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 + */ + +#ifndef __ABC_EXPORTER_H__ +#define __ABC_EXPORTER_H__ + +#include <Alembic/Abc/All.h> +#include <map> +#include <set> +#include <vector> + +#include "abc_util.h" + +class AbcObjectWriter; +class AbcTransformWriter; +class ArchiveWriter; + +struct Base; +struct Depsgraph; +struct Main; +struct Object; +struct Scene; +struct ViewLayer; + +struct ExportSettings { + ExportSettings(); + + Scene *scene; + /** Scene layer to export; all its objects will be exported, unless selected_only=true. */ + ViewLayer *view_layer; + Depsgraph *depsgraph; + SimpleLogger logger; + + bool selected_only; + bool visible_objects_only; + bool renderable_only; + + double frame_start, frame_end; + double frame_samples_xform; + double frame_samples_shape; + double shutter_open; + double shutter_close; + float global_scale; + + bool flatten_hierarchy; + + bool export_normals; + bool export_uvs; + bool export_vcols; + bool export_face_sets; + bool export_vweigths; + bool export_hair; + bool export_particles; + + bool apply_subdiv; + bool curves_as_mesh; + bool use_subdiv_schema; + bool export_child_hairs; + bool export_ogawa; + bool pack_uv; + bool triangulate; + + int quad_method; + int ngon_method; +}; + +class AbcExporter { + Main *m_bmain; + ExportSettings &m_settings; + + const char *m_filename; + + unsigned int m_trans_sampling_index, m_shape_sampling_index; + + ArchiveWriter *m_writer; + + /* 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: + AbcExporter(Main *bmain, const char *filename, ExportSettings &settings); + ~AbcExporter(); + + void operator()(short *do_update, float *progress, bool *was_canceled); + + protected: + void getShutterSamples(unsigned int nr_of_samples, + bool time_relative, + std::vector<double> &samples); + void getFrameSet(unsigned int nr_of_samples, std::set<double> &frames); + + private: + Alembic::Abc::TimeSamplingPtr createTimeSampling(double step); + + void createTransformWritersHierarchy(); + AbcTransformWriter *createTransformWriter(Object *ob, Object *parent, Object *dupliObParent); + void exploreTransform(Base *base, Object *object, Object *parent, Object *dupliObParent); + void exploreObject(Base *base, Object *object, Object *dupliObParent); + void createShapeWriters(); + void createShapeWriter(Object *ob, Object *dupliObParent); + void createParticleSystemsWriters(Object *ob, AbcTransformWriter *xform); + + AbcTransformWriter *getXForm(const std::string &name); + + void setCurrentFrame(Main *bmain, double t); +}; + +#endif /* __ABC_EXPORTER_H__ */ diff --git a/source/blender/io/alembic/intern/abc_reader_archive.cc b/source/blender/io/alembic/intern/abc_reader_archive.cc new file mode 100644 index 00000000000..6ad44553701 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_archive.cc @@ -0,0 +1,140 @@ +/* + * 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. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_reader_archive.h" + +extern "C" { +#include "BKE_main.h" + +#include "BLI_path_util.h" +#include "BLI_string.h" +} + +#ifdef WIN32 +# include "utfconv.h" +#endif + +#include <fstream> + +using Alembic::Abc::ErrorHandler; +using Alembic::Abc::Exception; +using Alembic::Abc::IArchive; +using Alembic::Abc::kWrapExisting; + +static IArchive open_archive(const std::string &filename, + const std::vector<std::istream *> &input_streams, + bool &is_hdf5) +{ + is_hdf5 = false; + + try { + Alembic::AbcCoreOgawa::ReadArchive archive_reader(input_streams); + + return IArchive(archive_reader(filename), kWrapExisting, ErrorHandler::kThrowPolicy); + } + catch (const Exception &e) { + std::cerr << e.what() << '\n'; + +#ifdef WITH_ALEMBIC_HDF5 + try { + is_hdf5 = true; + Alembic::AbcCoreAbstract::ReadArraySampleCachePtr cache_ptr; + + return IArchive(Alembic::AbcCoreHDF5::ReadArchive(), + filename.c_str(), + ErrorHandler::kThrowPolicy, + cache_ptr); + } + catch (const Exception &) { + std::cerr << e.what() << '\n'; + return IArchive(); + } +#else + /* Inspect the file to see whether it's really a HDF5 file. */ + char header[4]; /* char(0x89) + "HDF" */ + std::ifstream the_file(filename.c_str(), std::ios::in | std::ios::binary); + if (!the_file) { + std::cerr << "Unable to open " << filename << std::endl; + } + else if (!the_file.read(header, sizeof(header))) { + std::cerr << "Unable to read from " << filename << std::endl; + } + else if (strncmp(header + 1, "HDF", 3)) { + std::cerr << filename << " has an unknown file format, unable to read." << std::endl; + } + else { + is_hdf5 = true; + std::cerr << filename << " is in the obsolete HDF5 format, unable to read." << std::endl; + } + + if (the_file.is_open()) { + the_file.close(); + } + + return IArchive(); +#endif + } + + return IArchive(); +} + +ArchiveReader::ArchiveReader(struct Main *bmain, const char *filename) +{ + char abs_filename[FILE_MAX]; + BLI_strncpy(abs_filename, filename, FILE_MAX); + BLI_path_abs(abs_filename, BKE_main_blendfile_path(bmain)); + +#ifdef WIN32 + UTF16_ENCODE(abs_filename); + std::wstring wstr(abs_filename_16); + m_infile.open(wstr.c_str(), std::ios::in | std::ios::binary); + UTF16_UN_ENCODE(abs_filename); +#else + m_infile.open(abs_filename, std::ios::in | std::ios::binary); +#endif + + m_streams.push_back(&m_infile); + + m_archive = open_archive(abs_filename, m_streams, m_is_hdf5); + + /* We can't open an HDF5 file from a stream, so close it. */ + if (m_is_hdf5) { + m_infile.close(); + m_streams.clear(); + } +} + +bool ArchiveReader::is_hdf5() const +{ + return m_is_hdf5; +} + +bool ArchiveReader::valid() const +{ + return m_archive.valid(); +} + +Alembic::Abc::IObject ArchiveReader::getTop() +{ + return m_archive.getTop(); +} diff --git a/source/blender/io/alembic/intern/abc_reader_archive.h b/source/blender/io/alembic/intern/abc_reader_archive.h new file mode 100644 index 00000000000..bdb53bd0b8c --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_archive.h @@ -0,0 +1,67 @@ +/* + * 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. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_READER_ARCHIVE_H__ +#define __ABC_READER_ARCHIVE_H__ + +#include <Alembic/Abc/All.h> + +#ifdef WITH_ALEMBIC_HDF5 +# include <Alembic/AbcCoreHDF5/All.h> +#endif + +#include <Alembic/AbcCoreOgawa/All.h> + +#include <fstream> + +struct Main; +struct Scene; + +/* Wrappers around input and output archives. The goal is to be able to use + * streams so that unicode paths work on Windows (T49112), and to make sure that + * the stream objects remain valid as long as the archives are open. + */ + +class ArchiveReader { + Alembic::Abc::IArchive m_archive; + std::ifstream m_infile; + std::vector<std::istream *> m_streams; + bool m_is_hdf5; + + public: + ArchiveReader(struct Main *bmain, const char *filename); + + bool valid() const; + + /** + * Returns true when either Blender is compiled with HDF5 support and + * the archive was successfully opened (valid() will also return true), + * or when Blender was built without HDF5 support but a HDF5 file was + * detected (valid() will return false). + */ + bool is_hdf5() const; + + Alembic::Abc::IObject getTop(); +}; + +#endif /* __ABC_READER_ARCHIVE_H__ */ diff --git a/source/blender/io/alembic/intern/abc_reader_camera.cc b/source/blender/io/alembic/intern/abc_reader_camera.cc new file mode 100644 index 00000000000..ab506f32cbe --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_camera.cc @@ -0,0 +1,113 @@ +/* + * 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_reader_camera.h" +#include "abc_reader_transform.h" +#include "abc_util.h" + +extern "C" { +#include "DNA_camera_types.h" +#include "DNA_object_types.h" + +#include "BKE_camera.h" +#include "BKE_object.h" + +#include "BLI_math.h" +} + +using Alembic::AbcGeom::CameraSample; +using Alembic::AbcGeom::ICamera; +using Alembic::AbcGeom::ICompoundProperty; +using Alembic::AbcGeom::IFloatProperty; +using Alembic::AbcGeom::ISampleSelector; +using Alembic::AbcGeom::kWrapExisting; + +AbcCameraReader::AbcCameraReader(const Alembic::Abc::IObject &object, ImportSettings &settings) + : AbcObjectReader(object, settings) +{ + ICamera abc_cam(m_iobject, kWrapExisting); + m_schema = abc_cam.getSchema(); + + get_min_max_time(m_iobject, m_schema, m_min_time, m_max_time); +} + +bool AbcCameraReader::valid() const +{ + return m_schema.valid(); +} + +bool AbcCameraReader::accepts_object_type( + const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const +{ + if (!Alembic::AbcGeom::ICamera::matches(alembic_header)) { + *err_str = + "Object type mismatch, Alembic object path pointed to Camera when importing, but not any " + "more."; + return false; + } + + if (ob->type != OB_CAMERA) { + *err_str = "Object type mismatch, Alembic object path points to Camera."; + return false; + } + + return true; +} + +void AbcCameraReader::readObjectData(Main *bmain, const ISampleSelector &sample_sel) +{ + Camera *bcam = static_cast<Camera *>(BKE_camera_add(bmain, m_data_name.c_str())); + + CameraSample cam_sample; + m_schema.get(cam_sample, sample_sel); + + ICompoundProperty customDataContainer = m_schema.getUserProperties(); + + if (customDataContainer.valid() && customDataContainer.getPropertyHeader("stereoDistance") && + customDataContainer.getPropertyHeader("eyeSeparation")) { + IFloatProperty convergence_plane(customDataContainer, "stereoDistance"); + IFloatProperty eye_separation(customDataContainer, "eyeSeparation"); + + bcam->stereo.interocular_distance = eye_separation.getValue(sample_sel); + bcam->stereo.convergence_distance = convergence_plane.getValue(sample_sel); + } + + const float lens = static_cast<float>(cam_sample.getFocalLength()); + const float apperture_x = static_cast<float>(cam_sample.getHorizontalAperture()); + const float apperture_y = static_cast<float>(cam_sample.getVerticalAperture()); + const float h_film_offset = static_cast<float>(cam_sample.getHorizontalFilmOffset()); + const float v_film_offset = static_cast<float>(cam_sample.getVerticalFilmOffset()); + const float film_aspect = apperture_x / apperture_y; + + bcam->lens = lens; + bcam->sensor_x = apperture_x * 10; + bcam->sensor_y = apperture_y * 10; + bcam->shiftx = h_film_offset / apperture_x; + bcam->shifty = v_film_offset / apperture_y / film_aspect; + bcam->clip_start = max_ff(0.1f, static_cast<float>(cam_sample.getNearClippingPlane())); + bcam->clip_end = static_cast<float>(cam_sample.getFarClippingPlane()); + bcam->dof.focus_distance = static_cast<float>(cam_sample.getFocusDistance()); + bcam->dof.aperture_fstop = static_cast<float>(cam_sample.getFStop()); + + m_object = BKE_object_add_only_object(bmain, OB_CAMERA, m_object_name.c_str()); + m_object->data = bcam; +} diff --git a/source/blender/io/alembic/intern/abc_reader_camera.h b/source/blender/io/alembic/intern/abc_reader_camera.h new file mode 100644 index 00000000000..1d9763b0454 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_camera.h @@ -0,0 +1,40 @@ +/* + * 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 + */ + +#ifndef __ABC_READER_CAMERA_H__ +#define __ABC_READER_CAMERA_H__ + +#include "abc_reader_object.h" + +class AbcCameraReader : public AbcObjectReader { + Alembic::AbcGeom::ICameraSchema m_schema; + + public: + AbcCameraReader(const Alembic::Abc::IObject &object, ImportSettings &settings); + + bool valid() const; + bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const; + + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); +}; + +#endif /* __ABC_READER_CAMERA_H__ */ diff --git a/source/blender/io/alembic/intern/abc_reader_curves.cc b/source/blender/io/alembic/intern/abc_reader_curves.cc new file mode 100644 index 00000000000..1be164c7c94 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_curves.cc @@ -0,0 +1,354 @@ +/* + * 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. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_reader_curves.h" +#include "abc_reader_transform.h" +#include "abc_util.h" + +#include <cstdio> + +#include "MEM_guardedalloc.h" + +extern "C" { +#include "DNA_curve_types.h" +#include "DNA_object_types.h" + +#include "BLI_listbase.h" + +#include "BKE_curve.h" +#include "BKE_mesh.h" +#include "BKE_object.h" +} + +using Alembic::Abc::FloatArraySamplePtr; +using Alembic::Abc::Int32ArraySamplePtr; +using Alembic::Abc::P3fArraySamplePtr; +using Alembic::Abc::PropertyHeader; +using Alembic::Abc::UcharArraySamplePtr; + +using Alembic::AbcGeom::CurvePeriodicity; +using Alembic::AbcGeom::ICompoundProperty; +using Alembic::AbcGeom::ICurves; +using Alembic::AbcGeom::ICurvesSchema; +using Alembic::AbcGeom::IFloatGeomParam; +using Alembic::AbcGeom::IInt16Property; +using Alembic::AbcGeom::ISampleSelector; +using Alembic::AbcGeom::kWrapExisting; + +AbcCurveReader::AbcCurveReader(const Alembic::Abc::IObject &object, ImportSettings &settings) + : AbcObjectReader(object, settings) +{ + ICurves abc_curves(object, kWrapExisting); + m_curves_schema = abc_curves.getSchema(); + + get_min_max_time(m_iobject, m_curves_schema, m_min_time, m_max_time); +} + +bool AbcCurveReader::valid() const +{ + return m_curves_schema.valid(); +} + +bool AbcCurveReader::accepts_object_type( + const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const +{ + if (!Alembic::AbcGeom::ICurves::matches(alembic_header)) { + *err_str = + "Object type mismatch, Alembic object path pointed to Curves when importing, but not any " + "more."; + return false; + } + + if (ob->type != OB_CURVE) { + *err_str = "Object type mismatch, Alembic object path points to Curves."; + return false; + } + + return true; +} + +void AbcCurveReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) +{ + Curve *cu = BKE_curve_add(bmain, m_data_name.c_str(), OB_CURVE); + + cu->flag |= CU_DEFORM_FILL | CU_3D; + cu->actvert = CU_ACT_NONE; + cu->resolu = 1; + + ICompoundProperty user_props = m_curves_schema.getUserProperties(); + if (user_props) { + const PropertyHeader *header = user_props.getPropertyHeader(ABC_CURVE_RESOLUTION_U_PROPNAME); + if (header != NULL && header->isScalar() && IInt16Property::matches(*header)) { + IInt16Property resolu(user_props, header->getName()); + cu->resolu = resolu.getValue(sample_sel); + } + } + + m_object = BKE_object_add_only_object(bmain, OB_CURVE, m_object_name.c_str()); + m_object->data = cu; + + read_curve_sample(cu, m_curves_schema, sample_sel); + + if (has_animations(m_curves_schema, m_settings)) { + addCacheModifier(); + } +} + +void AbcCurveReader::read_curve_sample(Curve *cu, + const ICurvesSchema &schema, + const ISampleSelector &sample_sel) +{ + ICurvesSchema::Sample smp; + try { + smp = schema.getValue(sample_sel); + } + catch (Alembic::Util::Exception &ex) { + printf("Alembic: error reading curve sample for '%s/%s' at time %f: %s\n", + m_iobject.getFullName().c_str(), + schema.getName().c_str(), + sample_sel.getRequestedTime(), + ex.what()); + return; + } + + const Int32ArraySamplePtr num_vertices = smp.getCurvesNumVertices(); + const P3fArraySamplePtr positions = smp.getPositions(); + const FloatArraySamplePtr weights = smp.getPositionWeights(); + const FloatArraySamplePtr knots = smp.getKnots(); + const CurvePeriodicity periodicity = smp.getWrap(); + const UcharArraySamplePtr orders = smp.getOrders(); + + const IFloatGeomParam widths_param = schema.getWidthsParam(); + FloatArraySamplePtr radiuses; + + if (widths_param.valid()) { + IFloatGeomParam::Sample wsample = widths_param.getExpandedValue(sample_sel); + radiuses = wsample.getVals(); + } + + int knot_offset = 0; + + size_t idx = 0; + for (size_t i = 0; i < num_vertices->size(); i++) { + const int num_verts = (*num_vertices)[i]; + + Nurb *nu = static_cast<Nurb *>(MEM_callocN(sizeof(Nurb), "abc_getnurb")); + nu->resolu = cu->resolu; + nu->resolv = cu->resolv; + nu->pntsu = num_verts; + nu->pntsv = 1; + nu->flag |= CU_SMOOTH; + + switch (smp.getType()) { + case Alembic::AbcGeom::kCubic: + nu->orderu = 4; + break; + case Alembic::AbcGeom::kVariableOrder: + if (orders && orders->size() > i) { + nu->orderu = static_cast<short>((*orders)[i]); + break; + } + ATTR_FALLTHROUGH; + case Alembic::AbcGeom::kLinear: + default: + nu->orderu = 2; + } + + if (periodicity == Alembic::AbcGeom::kNonPeriodic) { + nu->flagu |= CU_NURB_ENDPOINT; + } + else if (periodicity == Alembic::AbcGeom::kPeriodic) { + nu->flagu |= CU_NURB_CYCLIC; + + /* Check the number of points which overlap, we don't have + * overlapping points in Blender, but other software do use them to + * indicate that a curve is actually cyclic. Usually the number of + * overlapping points is equal to the order/degree of the curve. + */ + + const int start = idx; + const int end = idx + num_verts; + int overlap = 0; + + for (int j = start, k = end - nu->orderu; j < nu->orderu; j++, k++) { + const Imath::V3f &p1 = (*positions)[j]; + const Imath::V3f &p2 = (*positions)[k]; + + if (p1 != p2) { + break; + } + + overlap++; + } + + /* TODO: Special case, need to figure out how it coincides with knots. */ + if (overlap == 0 && num_verts > 2 && (*positions)[start] == (*positions)[end - 1]) { + overlap = 1; + } + + /* There is no real cycles. */ + if (overlap == 0) { + nu->flagu &= ~CU_NURB_CYCLIC; + nu->flagu |= CU_NURB_ENDPOINT; + } + + nu->pntsu -= overlap; + } + + const bool do_weights = (weights != NULL) && (weights->size() > 1); + float weight = 1.0f; + + const bool do_radius = (radiuses != NULL) && (radiuses->size() > 1); + float radius = (radiuses && radiuses->size() == 1) ? (*radiuses)[0] : 1.0f; + + nu->type = CU_NURBS; + + nu->bp = static_cast<BPoint *>(MEM_callocN(sizeof(BPoint) * nu->pntsu, "abc_getnurb")); + BPoint *bp = nu->bp; + + for (int j = 0; j < nu->pntsu; j++, bp++, idx++) { + const Imath::V3f &pos = (*positions)[idx]; + + if (do_radius) { + radius = (*radiuses)[idx]; + } + + if (do_weights) { + weight = (*weights)[idx]; + } + + copy_zup_from_yup(bp->vec, pos.getValue()); + bp->vec[3] = weight; + bp->f1 = SELECT; + bp->radius = radius; + bp->weight = 1.0f; + } + + if (knots && knots->size() != 0) { + nu->knotsu = static_cast<float *>( + MEM_callocN(KNOTSU(nu) * sizeof(float), "abc_setsplineknotsu")); + + /* TODO: second check is temporary, for until the check for cycles is rock solid. */ + if (periodicity == Alembic::AbcGeom::kPeriodic && (KNOTSU(nu) == knots->size() - 2)) { + /* Skip first and last knots. */ + for (size_t i = 1; i < knots->size() - 1; i++) { + nu->knotsu[i - 1] = (*knots)[knot_offset + i]; + } + } + else { + /* TODO: figure out how to use the knots array from other + * software in this case. */ + BKE_nurb_knot_calc_u(nu); + } + + knot_offset += knots->size(); + } + else { + BKE_nurb_knot_calc_u(nu); + } + + BLI_addtail(BKE_curve_nurbs_get(cu), nu); + } +} + +/* NOTE: Alembic only stores data about control points, but the Mesh + * passed from the cache modifier contains the displist, which has more data + * than the control points, so to avoid corrupting the displist we modify the + * object directly and create a new Mesh from that. Also we might need to + * create new or delete existing NURBS in the curve. + */ +Mesh *AbcCurveReader::read_mesh(Mesh *existing_mesh, + const ISampleSelector &sample_sel, + int /*read_flag*/, + const char **err_str) +{ + ICurvesSchema::Sample sample; + + try { + sample = m_curves_schema.getValue(sample_sel); + } + catch (Alembic::Util::Exception &ex) { + *err_str = "Error reading curve sample; more detail on the console"; + printf("Alembic: error reading curve sample for '%s/%s' at time %f: %s\n", + m_iobject.getFullName().c_str(), + m_curves_schema.getName().c_str(), + sample_sel.getRequestedTime(), + ex.what()); + return existing_mesh; + } + + const P3fArraySamplePtr &positions = sample.getPositions(); + const Int32ArraySamplePtr num_vertices = sample.getCurvesNumVertices(); + + int vertex_idx = 0; + int curve_idx; + Curve *curve = static_cast<Curve *>(m_object->data); + + const int curve_count = BLI_listbase_count(&curve->nurb); + bool same_topology = curve_count == num_vertices->size(); + + if (same_topology) { + Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first); + for (curve_idx = 0; nurbs; nurbs = nurbs->next, curve_idx++) { + const int num_in_alembic = (*num_vertices)[curve_idx]; + const int num_in_blender = nurbs->pntsu; + + if (num_in_alembic != num_in_blender) { + same_topology = false; + break; + } + } + } + + if (!same_topology) { + BKE_nurbList_free(&curve->nurb); + read_curve_sample(curve, m_curves_schema, sample_sel); + } + else { + Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first); + for (curve_idx = 0; nurbs; nurbs = nurbs->next, curve_idx++) { + const int totpoint = (*num_vertices)[curve_idx]; + + if (nurbs->bp) { + BPoint *point = nurbs->bp; + + for (int i = 0; i < totpoint; i++, point++, vertex_idx++) { + const Imath::V3f &pos = (*positions)[vertex_idx]; + copy_zup_from_yup(point->vec, pos.getValue()); + } + } + else if (nurbs->bezt) { + BezTriple *bezier = nurbs->bezt; + + for (int i = 0; i < totpoint; i++, bezier++, vertex_idx++) { + const Imath::V3f &pos = (*positions)[vertex_idx]; + copy_zup_from_yup(bezier->vec[1], pos.getValue()); + } + } + } + } + + return BKE_mesh_new_nomain_from_curve(m_object); +} diff --git a/source/blender/io/alembic/intern/abc_reader_curves.h b/source/blender/io/alembic/intern/abc_reader_curves.h new file mode 100644 index 00000000000..1e4f28edc51 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_curves.h @@ -0,0 +1,56 @@ +/* + * 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. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_READER_CURVES_H__ +#define __ABC_READER_CURVES_H__ + +#include "abc_reader_object.h" +#include "abc_reader_mesh.h" + +struct Curve; + +#define ABC_CURVE_RESOLUTION_U_PROPNAME "blender:resolution" + +class AbcCurveReader : public AbcObjectReader { + Alembic::AbcGeom::ICurvesSchema m_curves_schema; + + public: + AbcCurveReader(const Alembic::Abc::IObject &object, ImportSettings &settings); + + bool valid() const; + bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const; + + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); + struct Mesh *read_mesh(struct Mesh *existing_mesh, + const Alembic::Abc::ISampleSelector &sample_sel, + int read_flag, + const char **err_str); + + void read_curve_sample(Curve *cu, + const Alembic::AbcGeom::ICurvesSchema &schema, + const Alembic::Abc::ISampleSelector &sample_selector); +}; + +#endif /* __ABC_READER_CURVES_H__ */ diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.cc b/source/blender/io/alembic/intern/abc_reader_mesh.cc new file mode 100644 index 00000000000..a4e412695c3 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_mesh.cc @@ -0,0 +1,889 @@ +/* + * 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_reader_mesh.h" +#include "abc_reader_transform.h" +#include "abc_util.h" + +#include <algorithm> + +#include "MEM_guardedalloc.h" + +extern "C" { +#include "DNA_material_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" + +#include "BLI_math_geom.h" + +#include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_mesh.h" +#include "BKE_modifier.h" +#include "BKE_object.h" +} + +using Alembic::Abc::Int32ArraySamplePtr; +using Alembic::Abc::P3fArraySamplePtr; + +using Alembic::AbcGeom::IFaceSet; +using Alembic::AbcGeom::IFaceSetSchema; +using Alembic::AbcGeom::IN3fGeomParam; +using Alembic::AbcGeom::IObject; +using Alembic::AbcGeom::IPolyMesh; +using Alembic::AbcGeom::IPolyMeshSchema; +using Alembic::AbcGeom::ISampleSelector; +using Alembic::AbcGeom::ISubD; +using Alembic::AbcGeom::ISubDSchema; +using Alembic::AbcGeom::IV2fGeomParam; +using Alembic::AbcGeom::kWrapExisting; +using Alembic::AbcGeom::N3fArraySample; +using Alembic::AbcGeom::N3fArraySamplePtr; +using Alembic::AbcGeom::UInt32ArraySamplePtr; +using Alembic::AbcGeom::V2fArraySamplePtr; + +/* NOTE: Alembic's polygon winding order is clockwise, to match with Renderman. */ + +/* Some helpers for mesh generation */ +namespace utils { + +static void build_mat_map(const Main *bmain, std::map<std::string, Material *> &mat_map) +{ + Material *material = static_cast<Material *>(bmain->materials.first); + + for (; material; material = static_cast<Material *>(material->id.next)) { + mat_map[material->id.name + 2] = material; + } +} + +static void assign_materials(Main *bmain, + Object *ob, + const std::map<std::string, int> &mat_index_map) +{ + bool can_assign = true; + std::map<std::string, int>::const_iterator it = mat_index_map.begin(); + + int matcount = 0; + for (; it != mat_index_map.end(); ++it, matcount++) { + if (!BKE_object_material_slot_add(bmain, ob)) { + can_assign = false; + break; + } + } + + /* TODO(kevin): use global map? */ + std::map<std::string, Material *> mat_map; + build_mat_map(bmain, mat_map); + + std::map<std::string, Material *>::iterator mat_iter; + + if (can_assign) { + it = mat_index_map.begin(); + + for (; it != mat_index_map.end(); ++it) { + std::string mat_name = it->first; + mat_iter = mat_map.find(mat_name.c_str()); + + Material *assigned_mat; + + if (mat_iter == mat_map.end()) { + assigned_mat = BKE_material_add(bmain, mat_name.c_str()); + mat_map[mat_name] = assigned_mat; + } + else { + assigned_mat = mat_iter->second; + } + + BKE_object_material_assign(bmain, ob, assigned_mat, it->second, BKE_MAT_ASSIGN_OBDATA); + } + } +} + +} /* namespace utils */ + +struct AbcMeshData { + Int32ArraySamplePtr face_indices; + Int32ArraySamplePtr face_counts; + + P3fArraySamplePtr positions; + P3fArraySamplePtr ceil_positions; + + V2fArraySamplePtr uvs; + UInt32ArraySamplePtr uvs_indices; +}; + +static void read_mverts_interp(MVert *mverts, + const P3fArraySamplePtr &positions, + const P3fArraySamplePtr &ceil_positions, + const float weight) +{ + float tmp[3]; + for (int i = 0; i < positions->size(); i++) { + MVert &mvert = mverts[i]; + const Imath::V3f &floor_pos = (*positions)[i]; + const Imath::V3f &ceil_pos = (*ceil_positions)[i]; + + interp_v3_v3v3(tmp, floor_pos.getValue(), ceil_pos.getValue(), weight); + copy_zup_from_yup(mvert.co, tmp); + + mvert.bweight = 0; + } +} + +static void read_mverts(CDStreamConfig &config, const AbcMeshData &mesh_data) +{ + MVert *mverts = config.mvert; + const P3fArraySamplePtr &positions = mesh_data.positions; + + if (config.weight != 0.0f && mesh_data.ceil_positions != NULL && + mesh_data.ceil_positions->size() == positions->size()) { + read_mverts_interp(mverts, positions, mesh_data.ceil_positions, config.weight); + return; + } + + read_mverts(mverts, positions, nullptr); +} + +void read_mverts(MVert *mverts, const P3fArraySamplePtr positions, const N3fArraySamplePtr normals) +{ + for (int i = 0; i < positions->size(); i++) { + MVert &mvert = mverts[i]; + Imath::V3f pos_in = (*positions)[i]; + + copy_zup_from_yup(mvert.co, pos_in.getValue()); + + mvert.bweight = 0; + + if (normals) { + Imath::V3f nor_in = (*normals)[i]; + + short no[3]; + normal_float_to_short_v3(no, nor_in.getValue()); + + copy_zup_from_yup(mvert.no, no); + } + } +} + +static void read_mpolys(CDStreamConfig &config, const AbcMeshData &mesh_data) +{ + MPoly *mpolys = config.mpoly; + MLoop *mloops = config.mloop; + MLoopUV *mloopuvs = config.mloopuv; + + const Int32ArraySamplePtr &face_indices = mesh_data.face_indices; + const Int32ArraySamplePtr &face_counts = mesh_data.face_counts; + const V2fArraySamplePtr &uvs = mesh_data.uvs; + const size_t uvs_size = uvs == nullptr ? 0 : uvs->size(); + + const UInt32ArraySamplePtr &uvs_indices = mesh_data.uvs_indices; + + const bool do_uvs = (mloopuvs && uvs && uvs_indices) && + (uvs_indices->size() == face_indices->size()); + unsigned int loop_index = 0; + unsigned int rev_loop_index = 0; + unsigned int uv_index = 0; + + for (int i = 0; i < face_counts->size(); i++) { + const int face_size = (*face_counts)[i]; + + MPoly &poly = mpolys[i]; + poly.loopstart = loop_index; + poly.totloop = face_size; + + /* Polygons are always assumed to be smooth-shaded. If the Alembic mesh should be flat-shaded, + * this is encoded in custom loop normals. See T71246. */ + poly.flag |= ME_SMOOTH; + + /* NOTE: Alembic data is stored in the reverse order. */ + rev_loop_index = loop_index + (face_size - 1); + + for (int f = 0; f < face_size; f++, loop_index++, rev_loop_index--) { + MLoop &loop = mloops[rev_loop_index]; + loop.v = (*face_indices)[loop_index]; + + if (do_uvs) { + MLoopUV &loopuv = mloopuvs[rev_loop_index]; + + uv_index = (*uvs_indices)[loop_index]; + + /* Some Alembic files are broken (or at least export UVs in a way we don't expect). */ + if (uv_index >= uvs_size) { + continue; + } + + loopuv.uv[0] = (*uvs)[uv_index][0]; + loopuv.uv[1] = (*uvs)[uv_index][1]; + } + } + } + + BKE_mesh_calc_edges(config.mesh, false, false); +} + +static void process_no_normals(CDStreamConfig &config) +{ + /* Absense of normals in the Alembic mesh is interpreted as 'smooth'. */ + BKE_mesh_calc_normals(config.mesh); +} + +static void process_loop_normals(CDStreamConfig &config, const N3fArraySamplePtr loop_normals_ptr) +{ + size_t loop_count = loop_normals_ptr->size(); + + if (loop_count == 0) { + process_no_normals(config); + return; + } + + float(*lnors)[3] = static_cast<float(*)[3]>( + MEM_malloc_arrayN(loop_count, sizeof(float[3]), "ABC::FaceNormals")); + + Mesh *mesh = config.mesh; + MPoly *mpoly = mesh->mpoly; + const N3fArraySample &loop_normals = *loop_normals_ptr; + int abc_index = 0; + for (int i = 0, e = mesh->totpoly; i < e; i++, mpoly++) { + /* As usual, ABC orders the loops in reverse. */ + for (int j = mpoly->totloop - 1; j >= 0; j--, abc_index++) { + int blender_index = mpoly->loopstart + j; + copy_zup_from_yup(lnors[blender_index], loop_normals[abc_index].getValue()); + } + } + + mesh->flag |= ME_AUTOSMOOTH; + BKE_mesh_set_custom_normals(mesh, lnors); + + MEM_freeN(lnors); +} + +static void process_vertex_normals(CDStreamConfig &config, + const N3fArraySamplePtr vertex_normals_ptr) +{ + size_t normals_count = vertex_normals_ptr->size(); + if (normals_count == 0) { + process_no_normals(config); + return; + } + + float(*vnors)[3] = static_cast<float(*)[3]>( + MEM_malloc_arrayN(normals_count, sizeof(float[3]), "ABC::VertexNormals")); + + const N3fArraySample &vertex_normals = *vertex_normals_ptr; + for (int index = 0; index < normals_count; index++) { + copy_zup_from_yup(vnors[index], vertex_normals[index].getValue()); + } + + config.mesh->flag |= ME_AUTOSMOOTH; + BKE_mesh_set_custom_normals_from_vertices(config.mesh, vnors); + MEM_freeN(vnors); +} + +static void process_normals(CDStreamConfig &config, + const IN3fGeomParam &normals, + const ISampleSelector &selector) +{ + if (!normals.valid()) { + process_no_normals(config); + return; + } + + IN3fGeomParam::Sample normsamp = normals.getExpandedValue(selector); + Alembic::AbcGeom::GeometryScope scope = normals.getScope(); + + switch (scope) { + case Alembic::AbcGeom::kFacevaryingScope: // 'Vertex Normals' in Houdini. + process_loop_normals(config, normsamp.getVals()); + break; + case Alembic::AbcGeom::kVertexScope: + case Alembic::AbcGeom::kVaryingScope: // 'Point Normals' in Houdini. + process_vertex_normals(config, normsamp.getVals()); + break; + case Alembic::AbcGeom::kConstantScope: + case Alembic::AbcGeom::kUniformScope: + case Alembic::AbcGeom::kUnknownScope: + process_no_normals(config); + break; + } +} + +ABC_INLINE void read_uvs_params(CDStreamConfig &config, + AbcMeshData &abc_data, + const IV2fGeomParam &uv, + const ISampleSelector &selector) +{ + if (!uv.valid()) { + return; + } + + IV2fGeomParam::Sample uvsamp; + uv.getIndexed(uvsamp, selector); + + abc_data.uvs = uvsamp.getVals(); + abc_data.uvs_indices = uvsamp.getIndices(); + + if (abc_data.uvs_indices->size() == config.totloop) { + std::string name = Alembic::Abc::GetSourceName(uv.getMetaData()); + + /* According to the convention, primary UVs should have had their name + * set using Alembic::Abc::SetSourceName, but you can't expect everyone + * to follow it! :) */ + if (name.empty()) { + name = uv.getName(); + } + + void *cd_ptr = config.add_customdata_cb(config.mesh, name.c_str(), CD_MLOOPUV); + config.mloopuv = static_cast<MLoopUV *>(cd_ptr); + } +} + +static void *add_customdata_cb(Mesh *mesh, const char *name, int data_type) +{ + CustomDataType cd_data_type = static_cast<CustomDataType>(data_type); + void *cd_ptr; + CustomData *loopdata; + int numloops; + + /* unsupported custom data type -- don't do anything. */ + if (!ELEM(cd_data_type, CD_MLOOPUV, CD_MLOOPCOL)) { + return NULL; + } + + loopdata = &mesh->ldata; + cd_ptr = CustomData_get_layer_named(loopdata, cd_data_type, name); + if (cd_ptr != NULL) { + /* layer already exists, so just return it. */ + return cd_ptr; + } + + /* Create a new layer. */ + numloops = mesh->totloop; + cd_ptr = CustomData_add_layer_named(loopdata, cd_data_type, CD_DEFAULT, NULL, numloops, name); + return cd_ptr; +} + +static void get_weight_and_index(CDStreamConfig &config, + Alembic::AbcCoreAbstract::TimeSamplingPtr time_sampling, + size_t samples_number) +{ + Alembic::AbcGeom::index_t i0, i1; + + config.weight = get_weight_and_index(config.time, time_sampling, samples_number, i0, i1); + + config.index = i0; + config.ceil_index = i1; +} + +static void read_mesh_sample(const std::string &iobject_full_name, + ImportSettings *settings, + const IPolyMeshSchema &schema, + const ISampleSelector &selector, + CDStreamConfig &config) +{ + const IPolyMeshSchema::Sample sample = schema.getValue(selector); + + AbcMeshData abc_mesh_data; + abc_mesh_data.face_counts = sample.getFaceCounts(); + abc_mesh_data.face_indices = sample.getFaceIndices(); + abc_mesh_data.positions = sample.getPositions(); + + get_weight_and_index(config, schema.getTimeSampling(), schema.getNumSamples()); + + if (config.weight != 0.0f) { + Alembic::AbcGeom::IPolyMeshSchema::Sample ceil_sample; + schema.get(ceil_sample, Alembic::Abc::ISampleSelector(config.ceil_index)); + abc_mesh_data.ceil_positions = ceil_sample.getPositions(); + } + + if ((settings->read_flag & MOD_MESHSEQ_READ_UV) != 0) { + read_uvs_params(config, abc_mesh_data, schema.getUVsParam(), selector); + } + + if ((settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0) { + read_mverts(config, abc_mesh_data); + } + + if ((settings->read_flag & MOD_MESHSEQ_READ_POLY) != 0) { + read_mpolys(config, abc_mesh_data); + process_normals(config, schema.getNormalsParam(), selector); + } + + if ((settings->read_flag & (MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR)) != 0) { + read_custom_data(iobject_full_name, schema.getArbGeomParams(), config, selector); + } +} + +CDStreamConfig get_config(Mesh *mesh) +{ + CDStreamConfig config; + + BLI_assert(mesh->mvert || mesh->totvert == 0); + + config.mesh = mesh; + config.mvert = mesh->mvert; + config.mloop = mesh->mloop; + config.mpoly = mesh->mpoly; + config.totloop = mesh->totloop; + config.totpoly = mesh->totpoly; + config.loopdata = &mesh->ldata; + config.add_customdata_cb = add_customdata_cb; + + return config; +} + +/* ************************************************************************** */ + +AbcMeshReader::AbcMeshReader(const IObject &object, ImportSettings &settings) + : AbcObjectReader(object, settings) +{ + m_settings->read_flag |= MOD_MESHSEQ_READ_ALL; + + IPolyMesh ipoly_mesh(m_iobject, kWrapExisting); + m_schema = ipoly_mesh.getSchema(); + + get_min_max_time(m_iobject, m_schema, m_min_time, m_max_time); +} + +bool AbcMeshReader::valid() const +{ + return m_schema.valid(); +} + +void AbcMeshReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) +{ + Mesh *mesh = BKE_mesh_add(bmain, m_data_name.c_str()); + + m_object = BKE_object_add_only_object(bmain, OB_MESH, m_object_name.c_str()); + m_object->data = mesh; + + Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, NULL); + if (read_mesh != mesh) { + /* XXX fixme after 2.80; mesh->flag isn't copied by BKE_mesh_nomain_to_mesh() */ + /* read_mesh can be freed by BKE_mesh_nomain_to_mesh(), so get the flag before that happens. */ + short autosmooth = (read_mesh->flag & ME_AUTOSMOOTH); + BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_MESH, true); + mesh->flag |= autosmooth; + } + + if (m_settings->validate_meshes) { + BKE_mesh_validate(mesh, false, false); + } + + readFaceSetsSample(bmain, mesh, sample_sel); + + if (has_animations(m_schema, m_settings)) { + addCacheModifier(); + } +} + +bool AbcMeshReader::accepts_object_type( + const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const +{ + if (!Alembic::AbcGeom::IPolyMesh::matches(alembic_header)) { + *err_str = + "Object type mismatch, Alembic object path pointed to PolyMesh when importing, but not " + "any more."; + return false; + } + + if (ob->type != OB_MESH) { + *err_str = "Object type mismatch, Alembic object path points to PolyMesh."; + return false; + } + + return true; +} + +bool AbcMeshReader::topology_changed(Mesh *existing_mesh, const ISampleSelector &sample_sel) +{ + IPolyMeshSchema::Sample sample; + try { + sample = m_schema.getValue(sample_sel); + } + catch (Alembic::Util::Exception &ex) { + printf("Alembic: error reading mesh sample for '%s/%s' at time %f: %s\n", + m_iobject.getFullName().c_str(), + m_schema.getName().c_str(), + sample_sel.getRequestedTime(), + ex.what()); + // A similar error in read_mesh() would just return existing_mesh. + return false; + } + + const P3fArraySamplePtr &positions = sample.getPositions(); + const Alembic::Abc::Int32ArraySamplePtr &face_indices = sample.getFaceIndices(); + const Alembic::Abc::Int32ArraySamplePtr &face_counts = sample.getFaceCounts(); + + return positions->size() != existing_mesh->totvert || + face_counts->size() != existing_mesh->totpoly || + face_indices->size() != existing_mesh->totloop; +} + +Mesh *AbcMeshReader::read_mesh(Mesh *existing_mesh, + const ISampleSelector &sample_sel, + int read_flag, + const char **err_str) +{ + IPolyMeshSchema::Sample sample; + try { + sample = m_schema.getValue(sample_sel); + } + catch (Alembic::Util::Exception &ex) { + if (err_str != nullptr) { + *err_str = "Error reading mesh sample; more detail on the console"; + } + printf("Alembic: error reading mesh sample for '%s/%s' at time %f: %s\n", + m_iobject.getFullName().c_str(), + m_schema.getName().c_str(), + sample_sel.getRequestedTime(), + ex.what()); + return existing_mesh; + } + + const P3fArraySamplePtr &positions = sample.getPositions(); + const Alembic::Abc::Int32ArraySamplePtr &face_indices = sample.getFaceIndices(); + const Alembic::Abc::Int32ArraySamplePtr &face_counts = sample.getFaceCounts(); + + Mesh *new_mesh = NULL; + + /* Only read point data when streaming meshes, unless we need to create new ones. */ + ImportSettings settings; + settings.read_flag |= read_flag; + + if (topology_changed(existing_mesh, sample_sel)) { + new_mesh = BKE_mesh_new_nomain_from_template( + existing_mesh, positions->size(), 0, 0, face_indices->size(), face_counts->size()); + + settings.read_flag |= MOD_MESHSEQ_READ_ALL; + } + else { + /* If the face count changed (e.g. by triangulation), only read points. + * This prevents crash from T49813. + * TODO(kevin): perhaps find a better way to do this? */ + if (face_counts->size() != existing_mesh->totpoly || + face_indices->size() != existing_mesh->totloop) { + settings.read_flag = MOD_MESHSEQ_READ_VERT; + + if (err_str) { + *err_str = + "Topology has changed, perhaps by triangulating the" + " mesh. Only vertices will be read!"; + } + } + } + + CDStreamConfig config = get_config(new_mesh ? new_mesh : existing_mesh); + config.time = sample_sel.getRequestedTime(); + + read_mesh_sample(m_iobject.getFullName(), &settings, m_schema, sample_sel, config); + + if (new_mesh) { + /* Here we assume that the number of materials doesn't change, i.e. that + * the material slots that were created when the object was loaded from + * Alembic are still valid now. */ + size_t num_polys = new_mesh->totpoly; + if (num_polys > 0) { + std::map<std::string, int> mat_map; + assign_facesets_to_mpoly(sample_sel, new_mesh->mpoly, num_polys, mat_map); + } + + return new_mesh; + } + + return existing_mesh; +} + +void AbcMeshReader::assign_facesets_to_mpoly(const ISampleSelector &sample_sel, + MPoly *mpoly, + int totpoly, + std::map<std::string, int> &r_mat_map) +{ + std::vector<std::string> face_sets; + m_schema.getFaceSetNames(face_sets); + + if (face_sets.empty()) { + return; + } + + int current_mat = 0; + + for (int i = 0; i < face_sets.size(); i++) { + const std::string &grp_name = face_sets[i]; + + if (r_mat_map.find(grp_name) == r_mat_map.end()) { + r_mat_map[grp_name] = 1 + current_mat++; + } + + const int assigned_mat = r_mat_map[grp_name]; + + const IFaceSet faceset = m_schema.getFaceSet(grp_name); + + if (!faceset.valid()) { + std::cerr << " Face set " << grp_name << " invalid for " << m_object_name << "\n"; + continue; + } + + const IFaceSetSchema face_schem = faceset.getSchema(); + const IFaceSetSchema::Sample face_sample = face_schem.getValue(sample_sel); + const Int32ArraySamplePtr group_faces = face_sample.getFaces(); + const size_t num_group_faces = group_faces->size(); + + for (size_t l = 0; l < num_group_faces; l++) { + size_t pos = (*group_faces)[l]; + + if (pos >= totpoly) { + std::cerr << "Faceset overflow on " << faceset.getName() << '\n'; + break; + } + + MPoly &poly = mpoly[pos]; + poly.mat_nr = assigned_mat - 1; + } + } +} + +void AbcMeshReader::readFaceSetsSample(Main *bmain, Mesh *mesh, const ISampleSelector &sample_sel) +{ + std::map<std::string, int> mat_map; + assign_facesets_to_mpoly(sample_sel, mesh->mpoly, mesh->totpoly, mat_map); + utils::assign_materials(bmain, m_object, mat_map); +} + +/* ************************************************************************** */ + +ABC_INLINE MEdge *find_edge(MEdge *edges, int totedge, int v1, int v2) +{ + for (int i = 0, e = totedge; i < e; i++) { + MEdge &edge = edges[i]; + + if (edge.v1 == v1 && edge.v2 == v2) { + return &edge; + } + } + + return NULL; +} + +static void read_subd_sample(const std::string &iobject_full_name, + ImportSettings *settings, + const ISubDSchema &schema, + const ISampleSelector &selector, + CDStreamConfig &config) +{ + const ISubDSchema::Sample sample = schema.getValue(selector); + + AbcMeshData abc_mesh_data; + abc_mesh_data.face_counts = sample.getFaceCounts(); + abc_mesh_data.face_indices = sample.getFaceIndices(); + abc_mesh_data.positions = sample.getPositions(); + + get_weight_and_index(config, schema.getTimeSampling(), schema.getNumSamples()); + + if (config.weight != 0.0f) { + Alembic::AbcGeom::ISubDSchema::Sample ceil_sample; + schema.get(ceil_sample, Alembic::Abc::ISampleSelector(config.ceil_index)); + abc_mesh_data.ceil_positions = ceil_sample.getPositions(); + } + + if ((settings->read_flag & MOD_MESHSEQ_READ_UV) != 0) { + read_uvs_params(config, abc_mesh_data, schema.getUVsParam(), selector); + } + + if ((settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0) { + read_mverts(config, abc_mesh_data); + } + + if ((settings->read_flag & MOD_MESHSEQ_READ_POLY) != 0) { + /* Alembic's 'SubD' scheme is used to store subdivision surfaces, i.e. the pre-subdivision + * mesh. Currently we don't add a subdivision modifier when we load such data. This code is + * assuming that the subdivided surface should be smooth. */ + read_mpolys(config, abc_mesh_data); + process_no_normals(config); + } + + if ((settings->read_flag & (MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR)) != 0) { + read_custom_data(iobject_full_name, schema.getArbGeomParams(), config, selector); + } +} + +/* ************************************************************************** */ + +AbcSubDReader::AbcSubDReader(const IObject &object, ImportSettings &settings) + : AbcObjectReader(object, settings) +{ + m_settings->read_flag |= MOD_MESHSEQ_READ_ALL; + + ISubD isubd_mesh(m_iobject, kWrapExisting); + m_schema = isubd_mesh.getSchema(); + + get_min_max_time(m_iobject, m_schema, m_min_time, m_max_time); +} + +bool AbcSubDReader::valid() const +{ + return m_schema.valid(); +} + +bool AbcSubDReader::accepts_object_type( + const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const +{ + if (!Alembic::AbcGeom::ISubD::matches(alembic_header)) { + *err_str = + "Object type mismatch, Alembic object path pointed to SubD when importing, but not any " + "more."; + return false; + } + + if (ob->type != OB_MESH) { + *err_str = "Object type mismatch, Alembic object path points to SubD."; + return false; + } + + return true; +} + +void AbcSubDReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) +{ + Mesh *mesh = BKE_mesh_add(bmain, m_data_name.c_str()); + + m_object = BKE_object_add_only_object(bmain, OB_MESH, m_object_name.c_str()); + m_object->data = mesh; + + Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, NULL); + if (read_mesh != mesh) { + BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_MESH, true); + } + + ISubDSchema::Sample sample; + try { + sample = m_schema.getValue(sample_sel); + } + catch (Alembic::Util::Exception &ex) { + printf("Alembic: error reading mesh sample for '%s/%s' at time %f: %s\n", + m_iobject.getFullName().c_str(), + m_schema.getName().c_str(), + sample_sel.getRequestedTime(), + ex.what()); + return; + } + + Int32ArraySamplePtr indices = sample.getCreaseIndices(); + Alembic::Abc::FloatArraySamplePtr sharpnesses = sample.getCreaseSharpnesses(); + + if (indices && sharpnesses) { + MEdge *edges = mesh->medge; + int totedge = mesh->totedge; + + for (int i = 0, s = 0, e = indices->size(); i < e; i += 2, s++) { + int v1 = (*indices)[i]; + int v2 = (*indices)[i + 1]; + + if (v2 < v1) { + /* It appears to be common to store edges with the smallest index first, in which case this + * prevents us from doing the second search below. */ + std::swap(v1, v2); + } + + MEdge *edge = find_edge(edges, totedge, v1, v2); + if (edge == NULL) { + edge = find_edge(edges, totedge, v2, v1); + } + + if (edge) { + edge->crease = unit_float_to_uchar_clamp((*sharpnesses)[s]); + } + } + + mesh->cd_flag |= ME_CDFLAG_EDGE_CREASE; + } + + if (m_settings->validate_meshes) { + BKE_mesh_validate(mesh, false, false); + } + + if (has_animations(m_schema, m_settings)) { + addCacheModifier(); + } +} + +Mesh *AbcSubDReader::read_mesh(Mesh *existing_mesh, + const ISampleSelector &sample_sel, + int read_flag, + const char **err_str) +{ + ISubDSchema::Sample sample; + try { + sample = m_schema.getValue(sample_sel); + } + catch (Alembic::Util::Exception &ex) { + if (err_str != nullptr) { + *err_str = "Error reading mesh sample; more detail on the console"; + } + printf("Alembic: error reading mesh sample for '%s/%s' at time %f: %s\n", + m_iobject.getFullName().c_str(), + m_schema.getName().c_str(), + sample_sel.getRequestedTime(), + ex.what()); + return existing_mesh; + } + + const P3fArraySamplePtr &positions = sample.getPositions(); + const Alembic::Abc::Int32ArraySamplePtr &face_indices = sample.getFaceIndices(); + const Alembic::Abc::Int32ArraySamplePtr &face_counts = sample.getFaceCounts(); + + Mesh *new_mesh = NULL; + + ImportSettings settings; + settings.read_flag |= read_flag; + + if (existing_mesh->totvert != positions->size()) { + new_mesh = BKE_mesh_new_nomain_from_template( + existing_mesh, positions->size(), 0, 0, face_indices->size(), face_counts->size()); + + settings.read_flag |= MOD_MESHSEQ_READ_ALL; + } + else { + /* If the face count changed (e.g. by triangulation), only read points. + * This prevents crash from T49813. + * TODO(kevin): perhaps find a better way to do this? */ + if (face_counts->size() != existing_mesh->totpoly || + face_indices->size() != existing_mesh->totloop) { + settings.read_flag = MOD_MESHSEQ_READ_VERT; + + if (err_str) { + *err_str = + "Topology has changed, perhaps by triangulating the" + " mesh. Only vertices will be read!"; + } + } + } + + /* Only read point data when streaming meshes, unless we need to create new ones. */ + CDStreamConfig config = get_config(new_mesh ? new_mesh : existing_mesh); + config.time = sample_sel.getRequestedTime(); + read_subd_sample(m_iobject.getFullName(), &settings, m_schema, sample_sel, config); + + return config.mesh; +} diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.h b/source/blender/io/alembic/intern/abc_reader_mesh.h new file mode 100644 index 00000000000..bc95c7ec134 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_mesh.h @@ -0,0 +1,86 @@ +/* + * 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 + */ + +#ifndef __ABC_READER_MESH_H__ +#define __ABC_READER_MESH_H__ + +#include "abc_customdata.h" +#include "abc_reader_object.h" + +struct Mesh; + +class AbcMeshReader : public AbcObjectReader { + Alembic::AbcGeom::IPolyMeshSchema m_schema; + + CDStreamConfig m_mesh_data; + + public: + AbcMeshReader(const Alembic::Abc::IObject &object, ImportSettings &settings); + + bool valid() const override; + bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const override; + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override; + + struct Mesh *read_mesh(struct Mesh *existing_mesh, + const Alembic::Abc::ISampleSelector &sample_sel, + int read_flag, + const char **err_str) override; + bool topology_changed(Mesh *existing_mesh, + const Alembic::Abc::ISampleSelector &sample_sel) override; + + private: + void readFaceSetsSample(Main *bmain, + Mesh *mesh, + const Alembic::AbcGeom::ISampleSelector &sample_sel); + + void assign_facesets_to_mpoly(const Alembic::Abc::ISampleSelector &sample_sel, + MPoly *mpoly, + int totpoly, + std::map<std::string, int> &r_mat_map); +}; + +class AbcSubDReader : public AbcObjectReader { + Alembic::AbcGeom::ISubDSchema m_schema; + + CDStreamConfig m_mesh_data; + + public: + AbcSubDReader(const Alembic::Abc::IObject &object, ImportSettings &settings); + + bool valid() const; + bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const; + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); + struct Mesh *read_mesh(struct Mesh *existing_mesh, + const Alembic::Abc::ISampleSelector &sample_sel, + int read_flag, + const char **err_str); +}; + +void read_mverts(MVert *mverts, + const Alembic::AbcGeom::P3fArraySamplePtr positions, + const Alembic::AbcGeom::N3fArraySamplePtr normals); + +CDStreamConfig get_config(struct Mesh *mesh); + +#endif /* __ABC_READER_MESH_H__ */ diff --git a/source/blender/io/alembic/intern/abc_reader_nurbs.cc b/source/blender/io/alembic/intern/abc_reader_nurbs.cc new file mode 100644 index 00000000000..0ada10baba5 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_nurbs.cc @@ -0,0 +1,225 @@ +/* + * 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_reader_nurbs.h" +#include "abc_reader_transform.h" +#include "abc_util.h" + +#include "MEM_guardedalloc.h" + +extern "C" { +#include "DNA_curve_types.h" +#include "DNA_object_types.h" + +#include "BLI_listbase.h" +#include "BLI_string.h" + +#include "BKE_curve.h" +#include "BKE_object.h" +} + +using Alembic::AbcGeom::FloatArraySamplePtr; +using Alembic::AbcGeom::kWrapExisting; +using Alembic::AbcGeom::MetaData; +using Alembic::AbcGeom::P3fArraySamplePtr; + +using Alembic::AbcGeom::ICompoundProperty; +using Alembic::AbcGeom::INuPatch; +using Alembic::AbcGeom::INuPatchSchema; +using Alembic::AbcGeom::IObject; + +AbcNurbsReader::AbcNurbsReader(const IObject &object, ImportSettings &settings) + : AbcObjectReader(object, settings) +{ + getNurbsPatches(m_iobject); + get_min_max_time(m_iobject, m_schemas[0].first, m_min_time, m_max_time); +} + +bool AbcNurbsReader::valid() const +{ + if (m_schemas.empty()) { + return false; + } + + std::vector<std::pair<INuPatchSchema, IObject>>::const_iterator it; + for (it = m_schemas.begin(); it != m_schemas.end(); ++it) { + const INuPatchSchema &schema = it->first; + + if (!schema.valid()) { + return false; + } + } + + return true; +} + +static bool set_knots(const FloatArraySamplePtr &knots, float *&nu_knots) +{ + if (!knots || knots->size() == 0) { + return false; + } + + /* Skip first and last knots, as they are used for padding. */ + const size_t num_knots = knots->size() - 2; + nu_knots = static_cast<float *>(MEM_callocN(num_knots * sizeof(float), "abc_setsplineknotsu")); + + for (size_t i = 0; i < num_knots; i++) { + nu_knots[i] = (*knots)[i + 1]; + } + + return true; +} + +void AbcNurbsReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) +{ + Curve *cu = static_cast<Curve *>(BKE_curve_add(bmain, "abc_curve", OB_SURF)); + cu->actvert = CU_ACT_NONE; + + std::vector<std::pair<INuPatchSchema, IObject>>::iterator it; + + for (it = m_schemas.begin(); it != m_schemas.end(); ++it) { + Nurb *nu = static_cast<Nurb *>(MEM_callocN(sizeof(Nurb), "abc_getnurb")); + nu->flag = CU_SMOOTH; + nu->type = CU_NURBS; + nu->resolu = cu->resolu; + nu->resolv = cu->resolv; + + const INuPatchSchema &schema = it->first; + INuPatchSchema::Sample smp; + try { + smp = schema.getValue(sample_sel); + } + catch (Alembic::Util::Exception &ex) { + printf("Alembic: error reading nurbs sample for '%s/%s' at time %f: %s\n", + m_iobject.getFullName().c_str(), + schema.getName().c_str(), + sample_sel.getRequestedTime(), + ex.what()); + return; + } + + nu->orderu = smp.getUOrder() - 1; + nu->orderv = smp.getVOrder() - 1; + nu->pntsu = smp.getNumU(); + nu->pntsv = smp.getNumV(); + + /* Read positions and weights. */ + + const P3fArraySamplePtr positions = smp.getPositions(); + const FloatArraySamplePtr weights = smp.getPositionWeights(); + + const size_t num_points = positions->size(); + + nu->bp = static_cast<BPoint *>(MEM_callocN(num_points * sizeof(BPoint), "abc_setsplinetype")); + + BPoint *bp = nu->bp; + float posw_in = 1.0f; + + for (int i = 0; i < num_points; i++, bp++) { + const Imath::V3f &pos_in = (*positions)[i]; + + if (weights) { + posw_in = (*weights)[i]; + } + + copy_zup_from_yup(bp->vec, pos_in.getValue()); + bp->vec[3] = posw_in; + bp->f1 = SELECT; + bp->radius = 1.0f; + bp->weight = 1.0f; + } + + /* Read knots. */ + + if (!set_knots(smp.getUKnot(), nu->knotsu)) { + BKE_nurb_knot_calc_u(nu); + } + + if (!set_knots(smp.getVKnot(), nu->knotsv)) { + BKE_nurb_knot_calc_v(nu); + } + + /* Read flags. */ + + ICompoundProperty user_props = schema.getUserProperties(); + + if (has_property(user_props, "enpoint_u")) { + nu->flagu |= CU_NURB_ENDPOINT; + } + + if (has_property(user_props, "enpoint_v")) { + nu->flagv |= CU_NURB_ENDPOINT; + } + + if (has_property(user_props, "cyclic_u")) { + nu->flagu |= CU_NURB_CYCLIC; + } + + if (has_property(user_props, "cyclic_v")) { + nu->flagv |= CU_NURB_CYCLIC; + } + + BLI_addtail(BKE_curve_nurbs_get(cu), nu); + } + + BLI_strncpy(cu->id.name + 2, m_data_name.c_str(), m_data_name.size() + 1); + + m_object = BKE_object_add_only_object(bmain, OB_SURF, m_object_name.c_str()); + m_object->data = cu; +} + +void AbcNurbsReader::getNurbsPatches(const IObject &obj) +{ + if (!obj.valid()) { + return; + } + + const int num_children = obj.getNumChildren(); + + if (num_children == 0) { + INuPatch abc_nurb(obj, kWrapExisting); + INuPatchSchema schem = abc_nurb.getSchema(); + m_schemas.push_back(std::pair<INuPatchSchema, IObject>(schem, obj)); + return; + } + + for (int i = 0; i < num_children; i++) { + bool ok = true; + IObject child(obj, obj.getChildHeader(i).getName()); + + if (!m_name.empty() && child.valid() && !begins_with(child.getFullName(), m_name)) { + ok = false; + } + + if (!child.valid()) { + continue; + } + + const MetaData &md = child.getMetaData(); + + if (INuPatch::matches(md) && ok) { + INuPatch abc_nurb(child, kWrapExisting); + INuPatchSchema schem = abc_nurb.getSchema(); + m_schemas.push_back(std::pair<INuPatchSchema, IObject>(schem, child)); + } + + getNurbsPatches(child); + } +} diff --git a/source/blender/io/alembic/intern/abc_reader_nurbs.h b/source/blender/io/alembic/intern/abc_reader_nurbs.h new file mode 100644 index 00000000000..f4284c136fb --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_nurbs.h @@ -0,0 +1,40 @@ +/* + * 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 + */ + +#ifndef __ABC_READER_NURBS_H__ +#define __ABC_READER_NURBS_H__ + +#include "abc_reader_object.h" + +class AbcNurbsReader : public AbcObjectReader { + std::vector<std::pair<Alembic::AbcGeom::INuPatchSchema, Alembic::Abc::IObject>> m_schemas; + + public: + AbcNurbsReader(const Alembic::Abc::IObject &object, ImportSettings &settings); + + bool valid() const; + + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); + + private: + void getNurbsPatches(const Alembic::Abc::IObject &obj); +}; + +#endif /* __ABC_READER_NURBS_H__ */ diff --git a/source/blender/io/alembic/intern/abc_reader_object.cc b/source/blender/io/alembic/intern/abc_reader_object.cc new file mode 100644 index 00000000000..3e7f87d78cc --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_object.cc @@ -0,0 +1,333 @@ +/* + * 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_reader_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_space_types.h" /* for FILE_MAX */ + +#include "BKE_constraint.h" +#include "BKE_lib_id.h" +#include "BKE_modifier.h" +#include "BKE_object.h" + +#include "BLI_utildefines.h" +#include "BLI_listbase.h" +#include "BLI_math_geom.h" +#include "BLI_string.h" +} + +using Alembic::AbcGeom::IObject; +using Alembic::AbcGeom::IXform; +using Alembic::AbcGeom::IXformSchema; + +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<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; + 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<float>(m0[i][j]); + } + } + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + mat1[i][j] = static_cast<float>(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; + float transform_from_alembic[4][4]; + + /* If the parent is a camera, apply the inverse rotation to make up for the from-Maya rotation. + * This assumes that the parent object also was imported from Alembic. */ + if (m_object->parent != nullptr && m_object->parent->type == OB_CAMERA) { + axis_angle_to_mat4_single(m_object->parentinv, 'X', -M_PI_2); + } + + this->read_matrix(transform_from_alembic, time, m_settings->scale, is_constant); + + /* Apply the matrix to the object. */ + BKE_object_apply_mat4(m_object, transform_from_alembic, true, false); + BKE_object_to_mat4(m_object, m_object->obmat); + + if (!is_constant) { + bConstraint *con = BKE_constraint_add_for_object( + m_object, NULL, CONSTRAINT_TYPE_TRANSFORM_CACHE); + bTransformCacheConstraint *data = static_cast<bTransformCacheConstraint *>(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] /* local matrix */, + 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_datatype(matrix, r_mat); + copy_m44_axis_swap(r_mat, r_mat, ABC_ZUP_FROM_YUP); + + /* Convert from Maya to Blender camera orientation. Children of this camera + * will have the opposite transform as their Parent Inverse matrix. + * See AbcObjectReader::setupObjectTransform(). */ + if (m_object->type == OB_CAMERA) { + float camera_rotation[4][4]; + axis_angle_to_mat4_single(camera_rotation, 'X', M_PI_2); + mul_m4_m4m4(r_mat, r_mat, camera_rotation); + } + + if (!m_inherits_xform) { + /* 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<MeshSeqCacheModifierData *>(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); +} diff --git a/source/blender/io/alembic/intern/abc_reader_object.h b/source/blender/io/alembic/intern/abc_reader_object.h new file mode 100644 index 00000000000..94923df2df9 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_object.h @@ -0,0 +1,171 @@ +/* + * 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 + */ + +#ifndef __ABC_READER_OBJECT_H__ +#define __ABC_READER_OBJECT_H__ + +#include <Alembic/Abc/All.h> +#include <Alembic/AbcGeom/All.h> + +extern "C" { +#include "DNA_ID.h" +} + +struct CacheFile; +struct Main; +struct Mesh; +struct Object; + +using Alembic::AbcCoreAbstract::chrono_t; + +struct ImportSettings { + bool do_convert_mat; + float conversion_mat[4][4]; + + int from_up; + int from_forward; + float scale; + bool is_sequence; + bool set_frame_range; + + /* Length and frame offset of file sequences. */ + int sequence_len; + int sequence_offset; + + /* From MeshSeqCacheModifierData.read_flag */ + int read_flag; + + bool validate_meshes; + + CacheFile *cache_file; + + ImportSettings() + : do_convert_mat(false), + from_up(0), + from_forward(0), + scale(1.0f), + is_sequence(false), + set_frame_range(false), + sequence_len(1), + sequence_offset(0), + read_flag(0), + validate_meshes(false), + cache_file(NULL) + { + } +}; + +template<typename Schema> static bool has_animations(Schema &schema, ImportSettings *settings) +{ + return settings->is_sequence || !schema.isConstant(); +} + +class AbcObjectReader { + protected: + std::string m_name; + std::string m_object_name; + std::string m_data_name; + Object *m_object; + Alembic::Abc::IObject m_iobject; + + ImportSettings *m_settings; + + chrono_t m_min_time; + chrono_t m_max_time; + + /* Use reference counting since the same reader may be used by multiple + * modifiers and/or constraints. */ + int m_refcount; + + bool m_inherits_xform; + + 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; + } + bool inherits_xform() const + { + return m_inherits_xform; + } + + virtual bool valid() const = 0; + virtual bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const = 0; + + virtual void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) = 0; + + virtual struct Mesh *read_mesh(struct Mesh *mesh, + const Alembic::Abc::ISampleSelector &sample_sel, + int read_flag, + const char **err_str); + virtual bool topology_changed(Mesh *existing_mesh, + const Alembic::Abc::ISampleSelector &sample_sel); + + /** Reads the object matrix and sets up an object transform if animated. */ + void setupObjectTransform(const float time); + + void addCacheModifier(); + + chrono_t minTime() const; + chrono_t maxTime() const; + + int refcount() const; + void incref(); + void decref(); + + void read_matrix(float r_mat[4][4], const float time, const float scale, bool &is_constant); + + protected: + void determine_inherits_xform(); +}; + +Imath::M44d get_matrix(const Alembic::AbcGeom::IXformSchema &schema, const float time); + +#endif /* __ABC_READER_OBJECT_H__ */ diff --git a/source/blender/io/alembic/intern/abc_reader_points.cc b/source/blender/io/alembic/intern/abc_reader_points.cc new file mode 100644 index 00000000000..e4dc345f868 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_points.cc @@ -0,0 +1,157 @@ +/* + * 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. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_reader_points.h" +#include "abc_reader_mesh.h" +#include "abc_reader_transform.h" +#include "abc_util.h" + +extern "C" { +#include "DNA_mesh_types.h" +#include "DNA_object_types.h" + +#include "BKE_customdata.h" +#include "BKE_mesh.h" +#include "BKE_object.h" +} + +using Alembic::AbcGeom::kWrapExisting; +using Alembic::AbcGeom::N3fArraySamplePtr; +using Alembic::AbcGeom::P3fArraySamplePtr; + +using Alembic::AbcGeom::ICompoundProperty; +using Alembic::AbcGeom::IN3fArrayProperty; +using Alembic::AbcGeom::IPoints; +using Alembic::AbcGeom::IPointsSchema; +using Alembic::AbcGeom::ISampleSelector; + +AbcPointsReader::AbcPointsReader(const Alembic::Abc::IObject &object, ImportSettings &settings) + : AbcObjectReader(object, settings) +{ + IPoints ipoints(m_iobject, kWrapExisting); + m_schema = ipoints.getSchema(); + get_min_max_time(m_iobject, m_schema, m_min_time, m_max_time); +} + +bool AbcPointsReader::valid() const +{ + return m_schema.valid(); +} + +bool AbcPointsReader::accepts_object_type( + const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const +{ + if (!Alembic::AbcGeom::IPoints::matches(alembic_header)) { + *err_str = + "Object type mismatch, Alembic object path pointed to Points when importing, but not any " + "more."; + return false; + } + + if (ob->type != OB_MESH) { + *err_str = "Object type mismatch, Alembic object path points to Points."; + return false; + } + + return true; +} + +void AbcPointsReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) +{ + Mesh *mesh = BKE_mesh_add(bmain, m_data_name.c_str()); + Mesh *read_mesh = this->read_mesh(mesh, sample_sel, 0, NULL); + + if (read_mesh != mesh) { + BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_MESH, true); + } + + if (m_settings->validate_meshes) { + BKE_mesh_validate(mesh, false, false); + } + + m_object = BKE_object_add_only_object(bmain, OB_MESH, m_object_name.c_str()); + m_object->data = mesh; + + if (has_animations(m_schema, m_settings)) { + addCacheModifier(); + } +} + +void read_points_sample(const IPointsSchema &schema, + const ISampleSelector &selector, + CDStreamConfig &config) +{ + Alembic::AbcGeom::IPointsSchema::Sample sample = schema.getValue(selector); + + const P3fArraySamplePtr &positions = sample.getPositions(); + + ICompoundProperty prop = schema.getArbGeomParams(); + N3fArraySamplePtr vnormals; + + if (has_property(prop, "N")) { + const Alembic::Util::uint32_t itime = static_cast<Alembic::Util::uint32_t>( + selector.getRequestedTime()); + const IN3fArrayProperty &normals_prop = IN3fArrayProperty(prop, "N", itime); + + if (normals_prop) { + vnormals = normals_prop.getValue(selector); + } + } + + read_mverts(config.mvert, positions, vnormals); +} + +struct Mesh *AbcPointsReader::read_mesh(struct Mesh *existing_mesh, + const ISampleSelector &sample_sel, + int /*read_flag*/, + const char **err_str) +{ + IPointsSchema::Sample sample; + try { + sample = m_schema.getValue(sample_sel); + } + catch (Alembic::Util::Exception &ex) { + *err_str = "Error reading points sample; more detail on the console"; + printf("Alembic: error reading points sample for '%s/%s' at time %f: %s\n", + m_iobject.getFullName().c_str(), + m_schema.getName().c_str(), + sample_sel.getRequestedTime(), + ex.what()); + return existing_mesh; + } + + const P3fArraySamplePtr &positions = sample.getPositions(); + + Mesh *new_mesh = NULL; + + if (existing_mesh->totvert != positions->size()) { + new_mesh = BKE_mesh_new_nomain(positions->size(), 0, 0, 0, 0); + } + + CDStreamConfig config = get_config(new_mesh ? new_mesh : existing_mesh); + read_points_sample(m_schema, sample_sel, config); + + return new_mesh ? new_mesh : existing_mesh; +} diff --git a/source/blender/io/alembic/intern/abc_reader_points.h b/source/blender/io/alembic/intern/abc_reader_points.h new file mode 100644 index 00000000000..31ad6c4589b --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_points.h @@ -0,0 +1,54 @@ +/* + * 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. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_READER_POINTS_H__ +#define __ABC_READER_POINTS_H__ + +#include "abc_reader_object.h" +#include "abc_customdata.h" + +class AbcPointsReader : public AbcObjectReader { + Alembic::AbcGeom::IPointsSchema m_schema; + Alembic::AbcGeom::IPointsSchema::Sample m_sample; + + public: + AbcPointsReader(const Alembic::Abc::IObject &object, ImportSettings &settings); + + bool valid() const; + bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const; + + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); + + struct Mesh *read_mesh(struct Mesh *existing_mesh, + const Alembic::Abc::ISampleSelector &sample_sel, + int read_flag, + const char **err_str); +}; + +void read_points_sample(const Alembic::AbcGeom::IPointsSchema &schema, + const Alembic::AbcGeom::ISampleSelector &selector, + CDStreamConfig &config); + +#endif /* __ABC_READER_POINTS_H__ */ diff --git a/source/blender/io/alembic/intern/abc_reader_transform.cc b/source/blender/io/alembic/intern/abc_reader_transform.cc new file mode 100644 index 00000000000..ce569a9ccb5 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_transform.cc @@ -0,0 +1,76 @@ +/* + * 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_reader_transform.h" +#include "abc_util.h" + +extern "C" { +#include "DNA_object_types.h" + +#include "BLI_utildefines.h" + +#include "BKE_object.h" +} + +using Alembic::Abc::ISampleSelector; + +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(); + + get_min_max_time(m_iobject, m_schema, m_min_time, m_max_time); +} + +bool AbcEmptyReader::valid() const +{ + return m_schema.valid(); +} + +bool AbcEmptyReader::accepts_object_type( + const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const +{ + if (!Alembic::AbcGeom::IXform::matches(alembic_header)) { + *err_str = + "Object type mismatch, Alembic object path pointed to XForm when importing, but not any " + "more."; + return false; + } + + if (ob->type != OB_EMPTY) { + *err_str = "Object type mismatch, Alembic object path points to XForm."; + return false; + } + + return true; +} + +void AbcEmptyReader::readObjectData(Main *bmain, const ISampleSelector &UNUSED(sample_sel)) +{ + m_object = BKE_object_add_only_object(bmain, OB_EMPTY, m_object_name.c_str()); + m_object->data = NULL; +} diff --git a/source/blender/io/alembic/intern/abc_reader_transform.h b/source/blender/io/alembic/intern/abc_reader_transform.h new file mode 100644 index 00000000000..6b4d23c1884 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_transform.h @@ -0,0 +1,42 @@ +/* + * 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 + */ + +#ifndef __ABC_READER_TRANSFORM_H__ +#define __ABC_READER_TRANSFORM_H__ + +#include "abc_reader_object.h" + +#include <Alembic/AbcGeom/All.h> + +class AbcEmptyReader : public AbcObjectReader { + Alembic::AbcGeom::IXformSchema m_schema; + + public: + AbcEmptyReader(const Alembic::Abc::IObject &object, ImportSettings &settings); + + bool valid() const; + bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const; + + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); +}; + +#endif /* __ABC_READER_TRANSFORM_H__ */ diff --git a/source/blender/io/alembic/intern/abc_util.cc b/source/blender/io/alembic/intern/abc_util.cc new file mode 100644 index 00000000000..b26ef8b3b76 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_util.cc @@ -0,0 +1,393 @@ +/* + * 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_util.h" + +#include "abc_reader_camera.h" +#include "abc_reader_curves.h" +#include "abc_reader_mesh.h" +#include "abc_reader_nurbs.h" +#include "abc_reader_points.h" +#include "abc_reader_transform.h" + +#include <Alembic/AbcMaterial/IMaterial.h> + +#include <algorithm> + +extern "C" { +#include "DNA_object_types.h" + +#include "BLI_math_geom.h" + +#include "PIL_time.h" +} + +std::string get_id_name(const Object *const ob) +{ + if (!ob) { + return ""; + } + + return get_id_name(&ob->id); +} + +std::string get_id_name(const ID *const id) +{ + std::string name(id->name + 2); + std::replace(name.begin(), name.end(), ' ', '_'); + std::replace(name.begin(), name.end(), '.', '_'); + std::replace(name.begin(), name.end(), ':', '_'); + + 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 + * "[../grandparent/]parent/object" if dupli_parent is NULL, or + * "dupli_parent/[../grandparent/]parent/object" otherwise. + * \param ob: + * \param dupli_parent: + * \return + */ +std::string get_object_dag_path_name(const Object *const ob, Object *dupli_parent) +{ + std::string name = get_id_name(ob); + + Object *p = ob->parent; + + while (p) { + name = get_id_name(p) + "/" + name; + p = p->parent; + } + + if (dupli_parent && (ob != dupli_parent)) { + name = get_id_name(dupli_parent) + "/" + name; + } + + return name; +} + +Imath::M44d convert_matrix_datatype(float mat[4][4]) +{ + Imath::M44d m; + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + m[i][j] = mat[i][j]; + } + } + + return m; +} + +void convert_matrix_datatype(const Imath::M44d &xform, float r_mat[4][4]) +{ + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + r_mat[i][j] = static_cast<float>(xform[i][j]); + } + } +} + +void split(const std::string &s, const char delim, std::vector<std::string> &tokens) +{ + tokens.clear(); + + std::stringstream ss(s); + std::string item; + + while (std::getline(ss, item, delim)) { + if (!item.empty()) { + tokens.push_back(item); + } + } +} + +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) +{ + const float rx = euler[0]; + 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: + ry = 0.0f; + rz = 0.0f; + BLI_assert(false); + break; + } + + unit_m3(rot_x_mat); + unit_m3(rot_y_mat); + unit_m3(rot_z_mat); + + rot_x_mat[1][1] = cos(rx); + rot_x_mat[2][1] = -sin(rx); + rot_x_mat[1][2] = sin(rx); + rot_x_mat[2][2] = cos(rx); + + rot_y_mat[2][2] = cos(ry); + rot_y_mat[0][2] = -sin(ry); + rot_y_mat[2][0] = sin(ry); + rot_y_mat[0][0] = cos(ry); + + rot_z_mat[0][0] = cos(rz); + rot_z_mat[1][0] = -sin(rz); + rot_z_mat[0][1] = sin(rz); + rot_z_mat[1][1] = cos(rz); +} + +/* 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 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 src_trans[3], dst_scale[3], src_scale[3], euler[3]; + + zero_v3(src_trans); + zero_v3(dst_scale); + zero_v3(src_scale); + zero_v3(euler); + unit_m3(src_rot); + unit_m3(dst_rot); + unit_m4(dst_scale_mat); + + /* TODO(Sybren): This code assumes there is no sheer component and no + * homogeneous scaling component, which is not always true when writing + * non-hierarchical (e.g. flat) objects (e.g. when parent has non-uniform + * scale and the child rotates). This is currently not taken into account + * when axis-swapping. */ + + /* 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_XZY, src_rot); + + /* Create X, Y, Z rotation matrices from euler angles. */ + create_swapped_rotation_matrix(rot_x_mat, rot_y_mat, rot_z_mat, euler, mode); + + /* Concatenate rotation matrices. */ + 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); + + mat3_to_eulO(euler, ROT_MODE_XZY, dst_rot); + + /* Start construction of dst_mat from rotation matrix */ + unit_m4(dst_mat); + copy_m4_m3(dst_mat, dst_rot); + + /* 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); + } + + /* 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]; + + size_to_mat4(dst_scale_mat, dst_scale); + mul_m4_m4m4(dst_mat, dst_mat, dst_scale_mat); +} + +/* 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], + AbcMatrixMode mode, + Object *proxy_from) +{ + float zup_mat[4][4]; + + /* get local or world matrix. */ + if (mode == ABC_MATRIX_LOCAL && obj->parent) { + /* 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); + } + else { + copy_m4_m4(zup_mat, obj->obmat); + } + + if (proxy_from) { + mul_m4_m4m4(zup_mat, proxy_from->obmat, zup_mat); + } + + copy_m44_axis_swap(r_yup_mat, zup_mat, ABC_YUP_FROM_ZUP); +} + +bool has_property(const Alembic::Abc::ICompoundProperty &prop, const std::string &name) +{ + if (!prop.valid()) { + return false; + } + + return prop.getPropertyHeader(name) != NULL; +} + +typedef std::pair<Alembic::AbcCoreAbstract::index_t, float> index_time_pair_t; + +float get_weight_and_index(float time, + const Alembic::AbcCoreAbstract::TimeSamplingPtr &time_sampling, + int samples_number, + Alembic::AbcGeom::index_t &i0, + Alembic::AbcGeom::index_t &i1) +{ + samples_number = std::max(samples_number, 1); + + index_time_pair_t t0 = time_sampling->getFloorIndex(time, samples_number); + i0 = i1 = t0.first; + + if (samples_number == 1 || (fabs(time - t0.second) < 0.0001f)) { + return 0.0f; + } + + index_time_pair_t t1 = time_sampling->getCeilIndex(time, samples_number); + i1 = t1.first; + + if (i0 == i1) { + return 0.0f; + } + + const float bias = (time - t0.second) / (t1.second - t0.second); + + if (fabs(1.0f - bias) < 0.0001f) { + i0 = i1; + return 0.0f; + } + + return bias; +} + +//#define USE_NURBS + +AbcObjectReader *create_reader(const Alembic::AbcGeom::IObject &object, ImportSettings &settings) +{ + AbcObjectReader *reader = NULL; + + const Alembic::AbcGeom::MetaData &md = object.getMetaData(); + + if (Alembic::AbcGeom::IXform::matches(md)) { + reader = new AbcEmptyReader(object, settings); + } + else if (Alembic::AbcGeom::IPolyMesh::matches(md)) { + reader = new AbcMeshReader(object, settings); + } + else if (Alembic::AbcGeom::ISubD::matches(md)) { + reader = new AbcSubDReader(object, settings); + } + else if (Alembic::AbcGeom::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); +#endif + } + else if (Alembic::AbcGeom::ICamera::matches(md)) { + reader = new AbcCameraReader(object, settings); + } + else if (Alembic::AbcGeom::IPoints::matches(md)) { + reader = new AbcPointsReader(object, settings); + } + else if (Alembic::AbcMaterial::IMaterial::matches(md)) { + /* Pass for now. */ + } + else if (Alembic::AbcGeom::ILight::matches(md)) { + /* Pass for now. */ + } + else if (Alembic::AbcGeom::IFaceSet::matches(md)) { + /* Pass, those are handled in the mesh reader. */ + } + else if (Alembic::AbcGeom::ICurves::matches(md)) { + reader = new AbcCurveReader(object, settings); + } + else { + std::cerr << "Alembic: unknown how to handle objects of schema '" << md.get("schemaObjTitle") + << "', skipping object '" << object.getFullName() << "'" << std::endl; + } + + return reader; +} + +/* ********************** */ + +ScopeTimer::ScopeTimer(const char *message) + : m_message(message), m_start(PIL_check_seconds_timer()) +{ +} + +ScopeTimer::~ScopeTimer() +{ + fprintf(stderr, "%s: %fs\n", m_message, PIL_check_seconds_timer() - m_start); +} + +/* ********************** */ + +std::string SimpleLogger::str() const +{ + return m_stream.str(); +} + +void SimpleLogger::clear() +{ + m_stream.clear(); + m_stream.str(""); +} + +std::ostringstream &SimpleLogger::stream() +{ + return m_stream; +} + +std::ostream &operator<<(std::ostream &os, const SimpleLogger &logger) +{ + os << logger.str(); + return os; +} diff --git a/source/blender/io/alembic/intern/abc_util.h b/source/blender/io/alembic/intern/abc_util.h new file mode 100644 index 00000000000..0b3462c2132 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_util.h @@ -0,0 +1,236 @@ +/* + * 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 + */ + +#ifndef __ABC_UTIL_H__ +#define __ABC_UTIL_H__ + +#include <Alembic/Abc/All.h> +#include <Alembic/AbcGeom/All.h> + +#ifdef _MSC_VER +# define ABC_INLINE static __forceinline +#else +# 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; +}; + +using Alembic::Abc::chrono_t; + +class AbcObjectReader; +struct ImportSettings; + +struct ID; +struct Object; + +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); + +/* Convert from float to Alembic matrix representations. Does NOT convert from Z-up to Y-up. */ +Imath::M44d convert_matrix_datatype(float mat[4][4]); +/* Convert from Alembic to float matrix representations. Does NOT convert from Y-up to Z-up. */ +void convert_matrix_datatype(const Imath::M44d &xform, float r_mat[4][4]); + +typedef enum { + ABC_MATRIX_WORLD = 1, + ABC_MATRIX_LOCAL = 2, +} AbcMatrixMode; +void create_transform_matrix(Object *obj, + float r_transform_mat[4][4], + AbcMatrixMode mode, + Object *proxy_from); + +void split(const std::string &s, const char delim, std::vector<std::string> &tokens); + +template<class TContainer> bool begins_with(const TContainer &input, const TContainer &match) +{ + return input.size() >= match.size() && std::equal(match.begin(), match.end(), input.begin()); +} + +template<typename Schema> +void get_min_max_time_ex(const Schema &schema, chrono_t &min, chrono_t &max) +{ + const Alembic::Abc::TimeSamplingPtr &time_samp = schema.getTimeSampling(); + + if (!schema.isConstant()) { + const size_t num_samps = schema.getNumSamples(); + + if (num_samps > 0) { + const chrono_t min_time = time_samp->getSampleTime(0); + min = std::min(min, min_time); + + const chrono_t max_time = time_samp->getSampleTime(num_samps - 1); + max = std::max(max, max_time); + } + } +} + +template<typename Schema> +void get_min_max_time(const Alembic::AbcGeom::IObject &object, + const Schema &schema, + chrono_t &min, + chrono_t &max) +{ + get_min_max_time_ex(schema, min, max); + + const Alembic::AbcGeom::IObject &parent = object.getParent(); + if (parent.valid() && Alembic::AbcGeom::IXform::matches(parent.getMetaData())) { + Alembic::AbcGeom::IXform xform(parent, Alembic::AbcGeom::kWrapExisting); + get_min_max_time_ex(xform.getSchema(), min, max); + } +} + +bool has_property(const Alembic::Abc::ICompoundProperty &prop, const std::string &name); + +float get_weight_and_index(float time, + const Alembic::AbcCoreAbstract::TimeSamplingPtr &time_sampling, + int samples_number, + Alembic::AbcGeom::index_t &i0, + Alembic::AbcGeom::index_t &i1); + +AbcObjectReader *create_reader(const Alembic::AbcGeom::IObject &object, ImportSettings &settings); + +/* ************************** */ + +/* TODO(kevin): for now keeping these transformations hardcoded to make sure + * everything works properly, and also because Alembic is almost exclusively + * used in Y-up software, but eventually they'll be set by the user in the UI + * like other importers/exporters do, to support other axis. */ + +/* Copy from Y-up to Z-up. */ + +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] = 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] = 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] = -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] = -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 swapped 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 + +class ScopeTimer { + const char *m_message; + double m_start; + + public: + ScopeTimer(const char *message); + ~ScopeTimer(); +}; + +#ifdef ABC_DEBUG_TIME +# define SCOPE_TIMER(message) ScopeTimer prof(message) +#else +# define SCOPE_TIMER(message) +#endif + +/* *************************** */ + +/** + * Utility class whose purpose is to more easily log related information. An + * instance of the SimpleLogger can be created in any context, and will hold a + * copy of all the strings passed to its output stream. + * + * Different instances of the class may be accessed from different threads, + * although accessing the same instance from different threads will lead to race + * conditions. + */ +class SimpleLogger { + std::ostringstream m_stream; + + public: + /** + * Return a copy of the string contained in the SimpleLogger's stream. + */ + std::string str() const; + + /** + * Remove the bits set on the SimpleLogger's stream and clear its string. + */ + void clear(); + + /** + * Return a reference to the SimpleLogger's stream, in order to e.g. push + * content into it. + */ + std::ostringstream &stream(); +}; + +#define ABC_LOG(logger) logger.stream() + +/** + * Pass the content of the logger's stream to the specified std::ostream. + */ +std::ostream &operator<<(std::ostream &os, const SimpleLogger &logger); + +#endif /* __ABC_UTIL_H__ */ diff --git a/source/blender/io/alembic/intern/abc_writer_archive.cc b/source/blender/io/alembic/intern/abc_writer_archive.cc new file mode 100644 index 00000000000..af18d480a18 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_archive.cc @@ -0,0 +1,114 @@ +/* + * 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. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_archive.h" +extern "C" { +#include "BKE_blender_version.h" + +#include "BLI_path_util.h" +#include "BLI_string.h" + +#include "DNA_scene_types.h" +} + +#ifdef WIN32 +# include "utfconv.h" +#endif + +#include <fstream> + +using Alembic::Abc::ErrorHandler; +using Alembic::Abc::kWrapExisting; +using Alembic::Abc::OArchive; + +/* This kinda duplicates CreateArchiveWithInfo, but Alembic does not seem to + * have a version supporting streams. */ +static OArchive create_archive(std::ostream *ostream, + const std::string &filename, + const std::string &scene_name, + double scene_fps, + bool ogawa) +{ + Alembic::Abc::MetaData abc_metadata; + + abc_metadata.set(Alembic::Abc::kApplicationNameKey, "Blender"); + abc_metadata.set(Alembic::Abc::kUserDescriptionKey, scene_name); + abc_metadata.set("blender_version", versionstr); + abc_metadata.set("FramesPerTimeUnit", std::to_string(scene_fps)); + + time_t raw_time; + time(&raw_time); + char buffer[128]; + +#if defined _WIN32 || defined _WIN64 + ctime_s(buffer, 128, &raw_time); +#else + ctime_r(&raw_time, buffer); +#endif + + const std::size_t buffer_len = strlen(buffer); + if (buffer_len > 0 && buffer[buffer_len - 1] == '\n') { + buffer[buffer_len - 1] = '\0'; + } + + abc_metadata.set(Alembic::Abc::kDateWrittenKey, buffer); + + ErrorHandler::Policy policy = ErrorHandler::kThrowPolicy; + +#ifdef WITH_ALEMBIC_HDF5 + if (!ogawa) { + return OArchive(Alembic::AbcCoreHDF5::WriteArchive(), filename, abc_metadata, policy); + } +#else + static_cast<void>(filename); + static_cast<void>(ogawa); +#endif + + Alembic::AbcCoreOgawa::WriteArchive archive_writer; + return OArchive(archive_writer(ostream, abc_metadata), kWrapExisting, policy); +} + +ArchiveWriter::ArchiveWriter(const char *filename, + const std::string &abc_scene_name, + const Scene *scene, + bool do_ogawa) +{ + /* Use stream to support unicode character paths on Windows. */ + if (do_ogawa) { +#ifdef WIN32 + UTF16_ENCODE(filename); + std::wstring wstr(filename_16); + m_outfile.open(wstr.c_str(), std::ios::out | std::ios::binary); + UTF16_UN_ENCODE(filename); +#else + m_outfile.open(filename, std::ios::out | std::ios::binary); +#endif + } + + m_archive = create_archive(&m_outfile, filename, abc_scene_name, FPS, do_ogawa); +} + +OArchive &ArchiveWriter::archive() +{ + return m_archive; +} diff --git a/source/blender/io/alembic/intern/abc_writer_archive.h b/source/blender/io/alembic/intern/abc_writer_archive.h new file mode 100644 index 00000000000..e261e60990a --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_archive.h @@ -0,0 +1,58 @@ +/* + * 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. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_WRITER_ARCHIVE_H__ +#define __ABC_WRITER_ARCHIVE_H__ + +#include <Alembic/Abc/All.h> + +#ifdef WITH_ALEMBIC_HDF5 +# include <Alembic/AbcCoreHDF5/All.h> +#endif + +#include <Alembic/AbcCoreOgawa/All.h> + +#include <fstream> + +struct Main; +struct Scene; + +/* Wrappers around input and output archives. The goal is to be able to use + * streams so that unicode paths work on Windows (T49112), and to make sure that + * the stream objects remain valid as long as the archives are open. + */ + +class ArchiveWriter { + std::ofstream m_outfile; + Alembic::Abc::OArchive m_archive; + + public: + ArchiveWriter(const char *filename, + const std::string &abc_scene_name, + const Scene *scene, + bool do_ogawa); + + Alembic::Abc::OArchive &archive(); +}; + +#endif /* __ABC_WRITER_ARCHIVE_H__ */ diff --git a/source/blender/io/alembic/intern/abc_writer_camera.cc b/source/blender/io/alembic/intern/abc_writer_camera.cc new file mode 100644 index 00000000000..e705e5ba911 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_camera.cc @@ -0,0 +1,81 @@ +/* + * 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_writer_camera.h" +#include "abc_writer_transform.h" + +extern "C" { +#include "DNA_camera_types.h" +#include "DNA_object_types.h" +} + +using Alembic::AbcGeom::OCamera; +using Alembic::AbcGeom::OFloatProperty; + +AbcCameraWriter::AbcCameraWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings) + : AbcObjectWriter(ob, time_sampling, settings, parent) +{ + OCamera camera(parent->alembicXform(), m_name, m_time_sampling); + m_camera_schema = camera.getSchema(); + + m_custom_data_container = m_camera_schema.getUserProperties(); + m_stereo_distance = OFloatProperty(m_custom_data_container, "stereoDistance", m_time_sampling); + m_eye_separation = OFloatProperty(m_custom_data_container, "eyeSeparation", m_time_sampling); +} + +void AbcCameraWriter::do_write() +{ + Camera *cam = static_cast<Camera *>(m_object->data); + + m_stereo_distance.set(cam->stereo.convergence_distance); + m_eye_separation.set(cam->stereo.interocular_distance); + + const double apperture_x = cam->sensor_x / 10.0; + const double apperture_y = cam->sensor_y / 10.0; + const double film_aspect = apperture_x / apperture_y; + + m_camera_sample.setFocalLength(cam->lens); + m_camera_sample.setHorizontalAperture(apperture_x); + m_camera_sample.setVerticalAperture(apperture_y); + m_camera_sample.setHorizontalFilmOffset(apperture_x * cam->shiftx); + m_camera_sample.setVerticalFilmOffset(apperture_y * cam->shifty * film_aspect); + m_camera_sample.setNearClippingPlane(cam->clip_start); + m_camera_sample.setFarClippingPlane(cam->clip_end); + + if (cam->dof.focus_object) { + Imath::V3f v(m_object->loc[0] - cam->dof.focus_object->loc[0], + m_object->loc[1] - cam->dof.focus_object->loc[1], + m_object->loc[2] - cam->dof.focus_object->loc[2]); + m_camera_sample.setFocusDistance(v.length()); + } + else { + m_camera_sample.setFocusDistance(cam->dof.focus_distance); + } + + /* Blender camera does not have an fstop param, so try to find a custom prop + * instead. */ + m_camera_sample.setFStop(cam->dof.aperture_fstop); + + m_camera_sample.setLensSqueezeRatio(1.0); + m_camera_schema.set(m_camera_sample); +} diff --git a/source/blender/io/alembic/intern/abc_writer_camera.h b/source/blender/io/alembic/intern/abc_writer_camera.h new file mode 100644 index 00000000000..3b515911a48 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_camera.h @@ -0,0 +1,45 @@ +/* + * 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 + */ + +#ifndef __ABC_WRITER_CAMERA_H__ +#define __ABC_WRITER_CAMERA_H__ + +#include "abc_writer_object.h" + +/* ************************************************************************** */ + +class AbcCameraWriter : public AbcObjectWriter { + Alembic::AbcGeom::OCameraSchema m_camera_schema; + Alembic::AbcGeom::CameraSample m_camera_sample; + Alembic::AbcGeom::OCompoundProperty m_custom_data_container; + Alembic::AbcGeom::OFloatProperty m_stereo_distance; + Alembic::AbcGeom::OFloatProperty m_eye_separation; + + public: + AbcCameraWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings); + + private: + virtual void do_write(); +}; + +#endif /* __ABC_WRITER_CAMERA_H__ */ diff --git a/source/blender/io/alembic/intern/abc_writer_curves.cc b/source/blender/io/alembic/intern/abc_writer_curves.cc new file mode 100644 index 00000000000..3ab9b365a72 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_curves.cc @@ -0,0 +1,189 @@ +/* + * 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. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_curves.h" +#include "abc_reader_curves.h" +#include "abc_writer_transform.h" + +extern "C" { +#include "DNA_curve_types.h" +#include "DNA_object_types.h" + +#include "BKE_curve.h" +#include "BKE_mesh.h" +#include "BKE_object.h" +} + +using Alembic::AbcGeom::OCompoundProperty; +using Alembic::AbcGeom::OCurves; +using Alembic::AbcGeom::OCurvesSchema; +using Alembic::AbcGeom::OInt16Property; +using Alembic::AbcGeom::ON3fGeomParam; +using Alembic::AbcGeom::OV2fGeomParam; + +AbcCurveWriter::AbcCurveWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings) + : AbcObjectWriter(ob, time_sampling, settings, parent) +{ + OCurves curves(parent->alembicXform(), m_name, m_time_sampling); + m_schema = curves.getSchema(); + + Curve *cu = static_cast<Curve *>(m_object->data); + OCompoundProperty user_props = m_schema.getUserProperties(); + OInt16Property user_prop_resolu(user_props, ABC_CURVE_RESOLUTION_U_PROPNAME); + user_prop_resolu.set(cu->resolu); +} + +void AbcCurveWriter::do_write() +{ + Curve *curve = static_cast<Curve *>(m_object->data); + + std::vector<Imath::V3f> verts; + std::vector<int32_t> vert_counts; + std::vector<float> widths; + std::vector<float> weights; + std::vector<float> knots; + std::vector<uint8_t> orders; + Imath::V3f temp_vert; + + Alembic::AbcGeom::BasisType curve_basis; + Alembic::AbcGeom::CurveType curve_type; + Alembic::AbcGeom::CurvePeriodicity periodicity; + + Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first); + for (; nurbs; nurbs = nurbs->next) { + if (nurbs->bp) { + curve_basis = Alembic::AbcGeom::kNoBasis; + curve_type = Alembic::AbcGeom::kVariableOrder; + + const int totpoint = nurbs->pntsu * nurbs->pntsv; + + const BPoint *point = nurbs->bp; + + for (int i = 0; i < totpoint; i++, point++) { + copy_yup_from_zup(temp_vert.getValue(), point->vec); + verts.push_back(temp_vert); + weights.push_back(point->vec[3]); + widths.push_back(point->radius); + } + } + else if (nurbs->bezt) { + curve_basis = Alembic::AbcGeom::kBezierBasis; + curve_type = Alembic::AbcGeom::kCubic; + + const int totpoint = nurbs->pntsu; + + const BezTriple *bezier = nurbs->bezt; + + /* TODO(kevin): store info about handles, Alembic doesn't have this. */ + for (int i = 0; i < totpoint; i++, bezier++) { + copy_yup_from_zup(temp_vert.getValue(), bezier->vec[1]); + verts.push_back(temp_vert); + widths.push_back(bezier->radius); + } + } + + if ((nurbs->flagu & CU_NURB_ENDPOINT) != 0) { + periodicity = Alembic::AbcGeom::kNonPeriodic; + } + else if ((nurbs->flagu & CU_NURB_CYCLIC) != 0) { + periodicity = Alembic::AbcGeom::kPeriodic; + + /* Duplicate the start points to indicate that the curve is actually + * cyclic since other software need those. + */ + + for (int i = 0; i < nurbs->orderu; i++) { + verts.push_back(verts[i]); + } + } + + if (nurbs->knotsu != NULL) { + const size_t num_knots = KNOTSU(nurbs); + + /* Add an extra knot at the beginning and end of the array since most apps + * require/expect them. */ + knots.resize(num_knots + 2); + + for (int i = 0; i < num_knots; i++) { + knots[i + 1] = nurbs->knotsu[i]; + } + + if ((nurbs->flagu & CU_NURB_CYCLIC) != 0) { + knots[0] = nurbs->knotsu[0]; + knots[num_knots - 1] = nurbs->knotsu[num_knots - 1]; + } + else { + knots[0] = (2.0f * nurbs->knotsu[0] - nurbs->knotsu[1]); + knots[num_knots - 1] = (2.0f * nurbs->knotsu[num_knots - 1] - + nurbs->knotsu[num_knots - 2]); + } + } + + orders.push_back(nurbs->orderu); + vert_counts.push_back(verts.size()); + } + + Alembic::AbcGeom::OFloatGeomParam::Sample width_sample; + width_sample.setVals(widths); + + m_sample = OCurvesSchema::Sample(verts, + vert_counts, + curve_type, + periodicity, + width_sample, + OV2fGeomParam::Sample(), /* UVs */ + ON3fGeomParam::Sample(), /* normals */ + curve_basis, + weights, + orders, + knots); + + m_sample.setSelfBounds(bounds()); + m_schema.set(m_sample); +} + +AbcCurveMeshWriter::AbcCurveMeshWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings) + : AbcGenericMeshWriter(ob, parent, time_sampling, settings) +{ +} + +Mesh *AbcCurveMeshWriter::getEvaluatedMesh(Scene * /*scene_eval*/, + Object *ob_eval, + bool &r_needsfree) +{ + Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval); + if (mesh_eval != NULL) { + /* Mesh_eval only exists when generative modifiers are in use. */ + r_needsfree = false; + return mesh_eval; + } + + r_needsfree = true; + return BKE_mesh_new_nomain_from_curve(ob_eval); +} diff --git a/source/blender/io/alembic/intern/abc_writer_curves.h b/source/blender/io/alembic/intern/abc_writer_curves.h new file mode 100644 index 00000000000..d6d8c0a7f11 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_curves.h @@ -0,0 +1,55 @@ +/* + * 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. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_WRITER_CURVES_H__ +#define __ABC_WRITER_CURVES_H__ + +#include "abc_writer_object.h" +#include "abc_writer_mesh.h" + +class AbcCurveWriter : public AbcObjectWriter { + Alembic::AbcGeom::OCurvesSchema m_schema; + Alembic::AbcGeom::OCurvesSchema::Sample m_sample; + + public: + AbcCurveWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings); + + protected: + void do_write(); +}; + +class AbcCurveMeshWriter : public AbcGenericMeshWriter { + public: + AbcCurveMeshWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings); + + protected: + Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree); +}; + +#endif /* __ABC_WRITER_CURVES_H__ */ diff --git a/source/blender/io/alembic/intern/abc_writer_hair.cc b/source/blender/io/alembic/intern/abc_writer_hair.cc new file mode 100644 index 00000000000..bbba03ed7f4 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_hair.cc @@ -0,0 +1,292 @@ +/* + * 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_writer_hair.h" +#include "abc_writer_transform.h" +#include "abc_util.h" + +#include <cstdio> + +extern "C" { +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" + +#include "BLI_math_geom.h" + +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_particle.h" +} + +using Alembic::Abc::P3fArraySamplePtr; + +using Alembic::AbcGeom::OCurves; +using Alembic::AbcGeom::OCurvesSchema; +using Alembic::AbcGeom::ON3fGeomParam; +using Alembic::AbcGeom::OV2fGeomParam; + +/* ************************************************************************** */ + +AbcHairWriter::AbcHairWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings, + ParticleSystem *psys) + : AbcObjectWriter(ob, time_sampling, settings, parent), m_uv_warning_shown(false) +{ + m_psys = psys; + + OCurves curves(parent->alembicXform(), psys->name, m_time_sampling); + m_schema = curves.getSchema(); +} + +void AbcHairWriter::do_write() +{ + if (!m_psys) { + return; + } + Mesh *mesh = mesh_get_eval_final( + m_settings.depsgraph, m_settings.scene, m_object, &CD_MASK_MESH); + BKE_mesh_tessface_ensure(mesh); + + std::vector<Imath::V3f> verts; + std::vector<int32_t> hvertices; + std::vector<Imath::V2f> uv_values; + std::vector<Imath::V3f> norm_values; + + if (m_psys->pathcache) { + ParticleSettings *part = m_psys->part; + bool export_children = m_settings.export_child_hairs && m_psys->childcache && + part->childtype != 0; + + if (!export_children || part->draw & PART_DRAW_PARENT) { + write_hair_sample(mesh, part, verts, norm_values, uv_values, hvertices); + } + + if (export_children) { + write_hair_child_sample(mesh, part, verts, norm_values, uv_values, hvertices); + } + } + + Alembic::Abc::P3fArraySample iPos(verts); + m_sample = OCurvesSchema::Sample(iPos, hvertices); + m_sample.setBasis(Alembic::AbcGeom::kNoBasis); + m_sample.setType(Alembic::AbcGeom::kLinear); + m_sample.setWrap(Alembic::AbcGeom::kNonPeriodic); + + if (!uv_values.empty()) { + OV2fGeomParam::Sample uv_smp; + uv_smp.setVals(uv_values); + m_sample.setUVs(uv_smp); + } + + if (!norm_values.empty()) { + ON3fGeomParam::Sample norm_smp; + norm_smp.setVals(norm_values); + m_sample.setNormals(norm_smp); + } + + m_sample.setSelfBounds(bounds()); + m_schema.set(m_sample); +} + +void AbcHairWriter::write_hair_sample(Mesh *mesh, + ParticleSettings *part, + std::vector<Imath::V3f> &verts, + std::vector<Imath::V3f> &norm_values, + std::vector<Imath::V2f> &uv_values, + std::vector<int32_t> &hvertices) +{ + /* Get untransformed vertices, there's a xform under the hair. */ + float inv_mat[4][4]; + invert_m4_m4_safe(inv_mat, m_object->obmat); + + MTFace *mtface = mesh->mtface; + MFace *mface = mesh->mface; + MVert *mverts = mesh->mvert; + + if ((!mtface || !mface) && !m_uv_warning_shown) { + std::fprintf(stderr, + "Warning, no UV set found for underlying geometry of %s.\n", + m_object->id.name + 2); + m_uv_warning_shown = true; + } + + ParticleData *pa = m_psys->particles; + int k; + + ParticleCacheKey **cache = m_psys->pathcache; + ParticleCacheKey *path; + float normal[3]; + Imath::V3f tmp_nor; + + for (int p = 0; p < m_psys->totpart; p++, pa++) { + /* underlying info for faces-only emission */ + path = cache[p]; + + /* Write UV and normal vectors */ + if (part->from == PART_FROM_FACE && mtface) { + const int num = pa->num_dmcache >= 0 ? pa->num_dmcache : pa->num; + + if (num < mesh->totface) { + /* TODO(Sybren): check whether the NULL check here and if(mface) are actually required */ + MFace *face = mface == NULL ? NULL : &mface[num]; + MTFace *tface = mtface + num; + + if (mface) { + float r_uv[2], mapfw[4], vec[3]; + + psys_interpolate_uvs(tface, face->v4, pa->fuv, r_uv); + uv_values.push_back(Imath::V2f(r_uv[0], r_uv[1])); + + psys_interpolate_face(mverts, face, tface, NULL, mapfw, vec, normal, NULL, NULL, NULL); + + copy_yup_from_zup(tmp_nor.getValue(), normal); + norm_values.push_back(tmp_nor); + } + } + else { + std::fprintf(stderr, "Particle to faces overflow (%d/%d)\n", num, mesh->totface); + } + } + else if (part->from == PART_FROM_VERT && mtface) { + /* vertex id */ + const int num = (pa->num_dmcache >= 0) ? pa->num_dmcache : pa->num; + + /* iterate over all faces to find a corresponding underlying UV */ + for (int n = 0; n < mesh->totface; n++) { + MFace *face = &mface[n]; + MTFace *tface = mtface + n; + unsigned int vtx[4]; + vtx[0] = face->v1; + vtx[1] = face->v2; + vtx[2] = face->v3; + vtx[3] = face->v4; + bool found = false; + + for (int o = 0; o < 4; o++) { + if (o > 2 && vtx[o] == 0) { + break; + } + + if (vtx[o] == num) { + uv_values.push_back(Imath::V2f(tface->uv[o][0], tface->uv[o][1])); + + MVert *mv = mverts + vtx[o]; + + normal_short_to_float_v3(normal, mv->no); + copy_yup_from_zup(tmp_nor.getValue(), normal); + norm_values.push_back(tmp_nor); + found = true; + break; + } + } + + if (found) { + break; + } + } + } + + int steps = path->segments + 1; + hvertices.push_back(steps); + + for (k = 0; k < steps; k++, path++) { + float vert[3]; + copy_v3_v3(vert, path->co); + mul_m4_v3(inv_mat, vert); + + /* Convert Z-up to Y-up. */ + verts.push_back(Imath::V3f(vert[0], vert[2], -vert[1])); + } + } +} + +void AbcHairWriter::write_hair_child_sample(Mesh *mesh, + ParticleSettings *part, + std::vector<Imath::V3f> &verts, + std::vector<Imath::V3f> &norm_values, + std::vector<Imath::V2f> &uv_values, + std::vector<int32_t> &hvertices) +{ + /* Get untransformed vertices, there's a xform under the hair. */ + float inv_mat[4][4]; + invert_m4_m4_safe(inv_mat, m_object->obmat); + + MTFace *mtface = mesh->mtface; + MVert *mverts = mesh->mvert; + + ParticleCacheKey **cache = m_psys->childcache; + ParticleCacheKey *path; + + ChildParticle *pc = m_psys->child; + + for (int p = 0; p < m_psys->totchild; p++, pc++) { + path = cache[p]; + + if (part->from == PART_FROM_FACE && part->childtype != PART_CHILD_PARTICLES && mtface) { + const int num = pc->num; + if (num < 0) { + ABC_LOG(m_settings.logger) + << "Warning, child particle of hair system " << m_psys->name + << " has unknown face index of geometry of " << (m_object->id.name + 2) + << ", skipping child hair." << std::endl; + continue; + } + + MFace *face = &mesh->mface[num]; + MTFace *tface = mtface + num; + + float r_uv[2], tmpnor[3], mapfw[4], vec[3]; + + psys_interpolate_uvs(tface, face->v4, pc->fuv, r_uv); + uv_values.push_back(Imath::V2f(r_uv[0], r_uv[1])); + + psys_interpolate_face(mverts, face, tface, NULL, mapfw, vec, tmpnor, NULL, NULL, NULL); + + /* Convert Z-up to Y-up. */ + norm_values.push_back(Imath::V3f(tmpnor[0], tmpnor[2], -tmpnor[1])); + } + else { + if (uv_values.size()) { + uv_values.push_back(uv_values[pc->parent]); + } + if (norm_values.size()) { + norm_values.push_back(norm_values[pc->parent]); + } + } + + int steps = path->segments + 1; + hvertices.push_back(steps); + + for (int k = 0; k < steps; k++) { + float vert[3]; + copy_v3_v3(vert, path->co); + mul_m4_v3(inv_mat, vert); + + /* Convert Z-up to Y-up. */ + verts.push_back(Imath::V3f(vert[0], vert[2], -vert[1])); + + path++; + } + } +} diff --git a/source/blender/io/alembic/intern/abc_writer_hair.h b/source/blender/io/alembic/intern/abc_writer_hair.h new file mode 100644 index 00000000000..67d1b7b3d23 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_hair.h @@ -0,0 +1,62 @@ +/* + * 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 + */ + +#ifndef __ABC_WRITER_HAIR_H__ +#define __ABC_WRITER_HAIR_H__ + +#include "abc_writer_object.h" + +struct ParticleSettings; +struct ParticleSystem; + +class AbcHairWriter : public AbcObjectWriter { + ParticleSystem *m_psys; + + Alembic::AbcGeom::OCurvesSchema m_schema; + Alembic::AbcGeom::OCurvesSchema::Sample m_sample; + + bool m_uv_warning_shown; + + public: + AbcHairWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings, + ParticleSystem *psys); + + private: + virtual void do_write(); + + void write_hair_sample(struct Mesh *mesh, + ParticleSettings *part, + std::vector<Imath::V3f> &verts, + std::vector<Imath::V3f> &norm_values, + std::vector<Imath::V2f> &uv_values, + std::vector<int32_t> &hvertices); + + void write_hair_child_sample(struct Mesh *mesh, + ParticleSettings *part, + std::vector<Imath::V3f> &verts, + std::vector<Imath::V3f> &norm_values, + std::vector<Imath::V2f> &uv_values, + std::vector<int32_t> &hvertices); +}; + +#endif /* __ABC_WRITER_HAIR_H__ */ diff --git a/source/blender/io/alembic/intern/abc_writer_mball.cc b/source/blender/io/alembic/intern/abc_writer_mball.cc new file mode 100644 index 00000000000..cc0775bd537 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_mball.cc @@ -0,0 +1,97 @@ +/* + * 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_writer_mball.h" +#include "abc_writer_mesh.h" + +extern "C" { +#include "DNA_meta_types.h" +#include "DNA_mesh_types.h" +#include "DNA_object_types.h" + +#include "BKE_displist.h" +#include "BKE_lib_id.h" +#include "BKE_mball.h" +#include "BKE_mesh.h" +#include "BKE_object.h" + +#include "BLI_utildefines.h" +} + +AbcMBallWriter::AbcMBallWriter(Main *bmain, + Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings) + : AbcGenericMeshWriter(ob, parent, time_sampling, settings), m_bmain(bmain) +{ + m_is_animated = isAnimated(); +} + +AbcMBallWriter::~AbcMBallWriter() +{ +} + +bool AbcMBallWriter::isAnimated() const +{ + return true; +} + +Mesh *AbcMBallWriter::getEvaluatedMesh(Scene * /*scene_eval*/, Object *ob_eval, bool &r_needsfree) +{ + Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval); + if (mesh_eval != NULL) { + /* Mesh_eval only exists when generative modifiers are in use. */ + r_needsfree = false; + return mesh_eval; + } + r_needsfree = true; + + /* The approach below is copied from BKE_mesh_new_from_object() */ + Mesh *tmpmesh = BKE_mesh_add(m_bmain, ((ID *)m_object->data)->name + 2); + BLI_assert(tmpmesh != NULL); + + /* BKE_mesh_add gives us a user count we don't need */ + id_us_min(&tmpmesh->id); + + ListBase disp = {NULL, NULL}; + /* TODO(sergey): This is gonna to work for until Depsgraph + * only contains for_render flag. As soon as CoW is + * implemented, this is to be rethought. + */ + BKE_displist_make_mball_forRender(m_settings.depsgraph, m_settings.scene, m_object, &disp); + BKE_mesh_from_metaball(&disp, tmpmesh); + BKE_displist_free(&disp); + + BKE_mesh_texspace_copy_from_object(tmpmesh, m_object); + + return tmpmesh; +} + +void AbcMBallWriter::freeEvaluatedMesh(struct Mesh *mesh) +{ + BKE_id_free(m_bmain, mesh); +} + +bool AbcMBallWriter::isBasisBall(Scene *scene, Object *ob) +{ + Object *basis_ob = BKE_mball_basis_find(scene, ob); + return ob == basis_ob; +} diff --git a/source/blender/io/alembic/intern/abc_writer_mball.h b/source/blender/io/alembic/intern/abc_writer_mball.h new file mode 100644 index 00000000000..c752472c86d --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_mball.h @@ -0,0 +1,56 @@ +/* + * 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 + */ + +#ifndef __ABC_WRITER_MBALL_H__ +#define __ABC_WRITER_MBALL_H__ + +#include "abc_writer_object.h" +#include "abc_writer_mesh.h" + +struct Main; +struct Object; + +/* AbcMBallWriter converts the metaballs to meshes at every frame, + * and defers to AbcGenericMeshWriter to perform the writing + * to the Alembic file. Only the basis balls are exported, as this + * results in the entire shape as one mesh. */ +class AbcMBallWriter : public AbcGenericMeshWriter { + Main *m_bmain; + + public: + explicit AbcMBallWriter(Main *bmain, + Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings); + + ~AbcMBallWriter(); + + static bool isBasisBall(Scene *scene, Object *ob); + + protected: + Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree) override; + void freeEvaluatedMesh(struct Mesh *mesh) override; + + private: + bool isAnimated() const override; +}; + +#endif /* __ABC_WRITER_MBALL_H__ */ diff --git a/source/blender/io/alembic/intern/abc_writer_mesh.cc b/source/blender/io/alembic/intern/abc_writer_mesh.cc new file mode 100644 index 00000000000..b55d2473f99 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_mesh.cc @@ -0,0 +1,592 @@ +/* + * 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_writer_mesh.h" +#include "abc_writer_transform.h" +#include "abc_util.h" + +extern "C" { +#include "DNA_material_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_fluidsim_types.h" + +#include "BKE_animsys.h" +#include "BKE_key.h" +#include "BKE_lib_id.h" +#include "BKE_material.h" +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_modifier.h" + +#include "bmesh.h" +#include "bmesh_tools.h" + +#include "DEG_depsgraph_query.h" +} + +using Alembic::Abc::FloatArraySample; +using Alembic::Abc::Int32ArraySample; +using Alembic::Abc::V2fArraySample; +using Alembic::Abc::V3fArraySample; + +using Alembic::AbcGeom::kFacevaryingScope; +using Alembic::AbcGeom::OBoolProperty; +using Alembic::AbcGeom::OCompoundProperty; +using Alembic::AbcGeom::OFaceSet; +using Alembic::AbcGeom::OFaceSetSchema; +using Alembic::AbcGeom::ON3fGeomParam; +using Alembic::AbcGeom::OPolyMesh; +using Alembic::AbcGeom::OPolyMeshSchema; +using Alembic::AbcGeom::OSubD; +using Alembic::AbcGeom::OSubDSchema; +using Alembic::AbcGeom::OV2fGeomParam; +using Alembic::AbcGeom::UInt32ArraySample; + +/* NOTE: Alembic's polygon winding order is clockwise, to match with Renderman. */ + +static void get_vertices(struct Mesh *mesh, std::vector<Imath::V3f> &points) +{ + points.clear(); + points.resize(mesh->totvert); + + MVert *verts = mesh->mvert; + + for (int i = 0, e = mesh->totvert; i < e; i++) { + copy_yup_from_zup(points[i].getValue(), verts[i].co); + } +} + +static void get_topology(struct Mesh *mesh, + std::vector<int32_t> &poly_verts, + std::vector<int32_t> &loop_counts, + bool &r_has_flat_shaded_poly) +{ + const int num_poly = mesh->totpoly; + const int num_loops = mesh->totloop; + MLoop *mloop = mesh->mloop; + MPoly *mpoly = mesh->mpoly; + r_has_flat_shaded_poly = false; + + poly_verts.clear(); + loop_counts.clear(); + poly_verts.reserve(num_loops); + loop_counts.reserve(num_poly); + + /* NOTE: data needs to be written in the reverse order. */ + for (int i = 0; i < num_poly; i++) { + MPoly &poly = mpoly[i]; + loop_counts.push_back(poly.totloop); + + r_has_flat_shaded_poly |= (poly.flag & ME_SMOOTH) == 0; + + MLoop *loop = mloop + poly.loopstart + (poly.totloop - 1); + + for (int j = 0; j < poly.totloop; j++, loop--) { + poly_verts.push_back(loop->v); + } + } +} + +static void get_creases(struct Mesh *mesh, + std::vector<int32_t> &indices, + std::vector<int32_t> &lengths, + std::vector<float> &sharpnesses) +{ + const float factor = 1.0f / 255.0f; + + indices.clear(); + lengths.clear(); + sharpnesses.clear(); + + MEdge *edge = mesh->medge; + + for (int i = 0, e = mesh->totedge; i < e; i++) { + const float sharpness = static_cast<float>(edge[i].crease) * factor; + + if (sharpness != 0.0f) { + indices.push_back(edge[i].v1); + indices.push_back(edge[i].v2); + sharpnesses.push_back(sharpness); + } + } + + lengths.resize(sharpnesses.size(), 2); +} + +static void get_loop_normals(struct Mesh *mesh, + std::vector<Imath::V3f> &normals, + bool has_flat_shaded_poly) +{ + normals.clear(); + + /* If all polygons are smooth shaded, and there are no custom normals, we don't need to export + * normals at all. This is also done by other software, see T71246. */ + if (!has_flat_shaded_poly && !CustomData_has_layer(&mesh->ldata, CD_CUSTOMLOOPNORMAL)) { + return; + } + + BKE_mesh_calc_normals_split(mesh); + const float(*lnors)[3] = static_cast<float(*)[3]>(CustomData_get_layer(&mesh->ldata, CD_NORMAL)); + BLI_assert(lnors != NULL || !"BKE_mesh_calc_normals_split() should have computed CD_NORMAL"); + + normals.resize(mesh->totloop); + + /* NOTE: data needs to be written in the reverse order. */ + int abc_index = 0; + MPoly *mp = mesh->mpoly; + for (int i = 0, e = mesh->totpoly; i < e; i++, mp++) { + for (int j = mp->totloop - 1; j >= 0; j--, abc_index++) { + int blender_index = mp->loopstart + j; + copy_yup_from_zup(normals[abc_index].getValue(), lnors[blender_index]); + } + } +} + +/* *************** Modifiers *************** */ + +/* check if the mesh is a subsurf, ignoring disabled modifiers and + * displace if it's after subsurf. */ +static ModifierData *get_subsurf_modifier(Scene *scene, Object *ob) +{ + ModifierData *md = static_cast<ModifierData *>(ob->modifiers.last); + + for (; md; md = md->prev) { + if (!modifier_isEnabled(scene, md, eModifierMode_Render)) { + continue; + } + + if (md->type == eModifierType_Subsurf) { + SubsurfModifierData *smd = reinterpret_cast<SubsurfModifierData *>(md); + + if (smd->subdivType == ME_CC_SUBSURF) { + return md; + } + } + + /* mesh is not a subsurf. break */ + if ((md->type != eModifierType_Displace) && (md->type != eModifierType_ParticleSystem)) { + return NULL; + } + } + + return NULL; +} + +static ModifierData *get_liquid_sim_modifier(Scene *scene, Object *ob) +{ + ModifierData *md = modifiers_findByType(ob, eModifierType_Fluidsim); + + if (md && (modifier_isEnabled(scene, md, eModifierMode_Render))) { + FluidsimModifierData *fsmd = reinterpret_cast<FluidsimModifierData *>(md); + + if (fsmd->fss && fsmd->fss->type == OB_FLUIDSIM_DOMAIN) { + return md; + } + } + + return NULL; +} + +/* ************************************************************************** */ + +AbcGenericMeshWriter::AbcGenericMeshWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings) + : AbcObjectWriter(ob, time_sampling, settings, parent) +{ + m_is_animated = isAnimated(); + m_subsurf_mod = NULL; + m_is_subd = false; + + /* If the object is static, use the default static time sampling. */ + if (!m_is_animated) { + time_sampling = 0; + } + + if (!m_settings.apply_subdiv) { + m_subsurf_mod = get_subsurf_modifier(m_settings.scene, m_object); + m_is_subd = (m_subsurf_mod != NULL); + } + + m_is_liquid = (get_liquid_sim_modifier(m_settings.scene, m_object) != NULL); + + while (parent->alembicXform().getChildHeader(m_name)) { + m_name.append("_"); + } + + if (m_settings.use_subdiv_schema && m_is_subd) { + OSubD subd(parent->alembicXform(), m_name, m_time_sampling); + m_subdiv_schema = subd.getSchema(); + } + else { + OPolyMesh mesh(parent->alembicXform(), m_name, m_time_sampling); + m_mesh_schema = mesh.getSchema(); + + OCompoundProperty typeContainer = m_mesh_schema.getUserProperties(); + OBoolProperty type(typeContainer, "meshtype"); + type.set(m_is_subd); + } +} + +AbcGenericMeshWriter::~AbcGenericMeshWriter() +{ + if (m_subsurf_mod) { + m_subsurf_mod->mode &= ~eModifierMode_DisableTemporary; + } +} + +bool AbcGenericMeshWriter::isAnimated() const +{ + if (BKE_animdata_id_is_animated(static_cast<ID *>(m_object->data))) { + return true; + } + if (BKE_key_from_object(m_object) != NULL) { + return true; + } + + /* Test modifiers. */ + ModifierData *md = static_cast<ModifierData *>(m_object->modifiers.first); + while (md) { + + if (md->type != eModifierType_Subsurf) { + return true; + } + + md = md->next; + } + + return false; +} + +void AbcGenericMeshWriter::setIsAnimated(bool is_animated) +{ + m_is_animated = is_animated; +} + +void AbcGenericMeshWriter::do_write() +{ + /* We have already stored a sample for this object. */ + if (!m_first_frame && !m_is_animated) { + return; + } + + bool needsfree; + struct Mesh *mesh = getFinalMesh(needsfree); + + try { + if (m_settings.use_subdiv_schema && m_subdiv_schema.valid()) { + writeSubD(mesh); + } + else { + writeMesh(mesh); + } + + if (needsfree) { + freeEvaluatedMesh(mesh); + } + } + catch (...) { + if (needsfree) { + freeEvaluatedMesh(mesh); + } + throw; + } +} + +void AbcGenericMeshWriter::freeEvaluatedMesh(struct Mesh *mesh) +{ + BKE_id_free(NULL, mesh); +} + +void AbcGenericMeshWriter::writeMesh(struct Mesh *mesh) +{ + std::vector<Imath::V3f> points, normals; + std::vector<int32_t> poly_verts, loop_counts; + std::vector<Imath::V3f> velocities; + bool has_flat_shaded_poly = false; + + get_vertices(mesh, points); + get_topology(mesh, poly_verts, loop_counts, has_flat_shaded_poly); + + if (m_first_frame && m_settings.export_face_sets) { + writeFaceSets(mesh, m_mesh_schema); + } + + m_mesh_sample = OPolyMeshSchema::Sample( + V3fArraySample(points), Int32ArraySample(poly_verts), Int32ArraySample(loop_counts)); + + UVSample sample; + if (m_first_frame && m_settings.export_uvs) { + const char *name = get_uv_sample(sample, m_custom_data_config, &mesh->ldata); + + if (!sample.indices.empty() && !sample.uvs.empty()) { + OV2fGeomParam::Sample uv_sample; + uv_sample.setVals(V2fArraySample(sample.uvs)); + uv_sample.setIndices(UInt32ArraySample(sample.indices)); + uv_sample.setScope(kFacevaryingScope); + + m_mesh_schema.setUVSourceName(name); + m_mesh_sample.setUVs(uv_sample); + } + + write_custom_data( + m_mesh_schema.getArbGeomParams(), m_custom_data_config, &mesh->ldata, CD_MLOOPUV); + } + + if (m_settings.export_normals) { + get_loop_normals(mesh, normals, has_flat_shaded_poly); + + ON3fGeomParam::Sample normals_sample; + if (!normals.empty()) { + normals_sample.setScope(kFacevaryingScope); + normals_sample.setVals(V3fArraySample(normals)); + } + + m_mesh_sample.setNormals(normals_sample); + } + + if (m_is_liquid) { + getVelocities(mesh, velocities); + m_mesh_sample.setVelocities(V3fArraySample(velocities)); + } + + m_mesh_sample.setSelfBounds(bounds()); + + m_mesh_schema.set(m_mesh_sample); + + writeArbGeoParams(mesh); +} + +void AbcGenericMeshWriter::writeSubD(struct Mesh *mesh) +{ + std::vector<float> crease_sharpness; + std::vector<Imath::V3f> points; + std::vector<int32_t> poly_verts, loop_counts; + std::vector<int32_t> crease_indices, crease_lengths; + bool has_flat_poly = false; + + get_vertices(mesh, points); + get_topology(mesh, poly_verts, loop_counts, has_flat_poly); + get_creases(mesh, crease_indices, crease_lengths, crease_sharpness); + + if (m_first_frame && m_settings.export_face_sets) { + writeFaceSets(mesh, m_subdiv_schema); + } + + m_subdiv_sample = OSubDSchema::Sample( + V3fArraySample(points), Int32ArraySample(poly_verts), Int32ArraySample(loop_counts)); + + UVSample sample; + if (m_first_frame && m_settings.export_uvs) { + const char *name = get_uv_sample(sample, m_custom_data_config, &mesh->ldata); + + if (!sample.indices.empty() && !sample.uvs.empty()) { + OV2fGeomParam::Sample uv_sample; + uv_sample.setVals(V2fArraySample(sample.uvs)); + uv_sample.setIndices(UInt32ArraySample(sample.indices)); + uv_sample.setScope(kFacevaryingScope); + + m_subdiv_schema.setUVSourceName(name); + m_subdiv_sample.setUVs(uv_sample); + } + + write_custom_data( + m_subdiv_schema.getArbGeomParams(), m_custom_data_config, &mesh->ldata, CD_MLOOPUV); + } + + if (!crease_indices.empty()) { + m_subdiv_sample.setCreaseIndices(Int32ArraySample(crease_indices)); + m_subdiv_sample.setCreaseLengths(Int32ArraySample(crease_lengths)); + m_subdiv_sample.setCreaseSharpnesses(FloatArraySample(crease_sharpness)); + } + + m_subdiv_sample.setSelfBounds(bounds()); + m_subdiv_schema.set(m_subdiv_sample); + + writeArbGeoParams(mesh); +} + +template<typename Schema> void AbcGenericMeshWriter::writeFaceSets(struct Mesh *me, Schema &schema) +{ + std::map<std::string, std::vector<int32_t>> geo_groups; + getGeoGroups(me, geo_groups); + + std::map<std::string, std::vector<int32_t>>::iterator it; + for (it = geo_groups.begin(); it != geo_groups.end(); ++it) { + OFaceSet face_set = schema.createFaceSet(it->first); + OFaceSetSchema::Sample samp; + samp.setFaces(Int32ArraySample(it->second)); + face_set.getSchema().set(samp); + } +} + +Mesh *AbcGenericMeshWriter::getFinalMesh(bool &r_needsfree) +{ + /* We don't want subdivided mesh data */ + if (m_subsurf_mod) { + m_subsurf_mod->mode |= eModifierMode_DisableTemporary; + } + + r_needsfree = false; + + Scene *scene = DEG_get_evaluated_scene(m_settings.depsgraph); + Object *ob_eval = DEG_get_evaluated_object(m_settings.depsgraph, m_object); + struct Mesh *mesh = getEvaluatedMesh(scene, ob_eval, r_needsfree); + + if (m_subsurf_mod) { + m_subsurf_mod->mode &= ~eModifierMode_DisableTemporary; + } + + if (m_settings.triangulate) { + const bool tag_only = false; + const int quad_method = m_settings.quad_method; + const int ngon_method = m_settings.ngon_method; + + struct BMeshCreateParams bmcp = {false}; + struct BMeshFromMeshParams bmfmp = {true, false, false, 0}; + BMesh *bm = BKE_mesh_to_bmesh_ex(mesh, &bmcp, &bmfmp); + + BM_mesh_triangulate(bm, quad_method, ngon_method, 4, tag_only, NULL, NULL, NULL); + + Mesh *result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, mesh); + BM_mesh_free(bm); + + if (r_needsfree) { + BKE_id_free(NULL, mesh); + } + + mesh = result; + r_needsfree = true; + } + + m_custom_data_config.pack_uvs = m_settings.pack_uv; + m_custom_data_config.mpoly = mesh->mpoly; + m_custom_data_config.mloop = mesh->mloop; + m_custom_data_config.totpoly = mesh->totpoly; + m_custom_data_config.totloop = mesh->totloop; + m_custom_data_config.totvert = mesh->totvert; + + return mesh; +} + +void AbcGenericMeshWriter::writeArbGeoParams(struct Mesh *me) +{ + if (m_is_liquid) { + /* We don't need anything more for liquid meshes. */ + return; + } + + if (m_first_frame && m_settings.export_vcols) { + if (m_subdiv_schema.valid()) { + write_custom_data( + m_subdiv_schema.getArbGeomParams(), m_custom_data_config, &me->ldata, CD_MLOOPCOL); + } + else { + write_custom_data( + m_mesh_schema.getArbGeomParams(), m_custom_data_config, &me->ldata, CD_MLOOPCOL); + } + } +} + +void AbcGenericMeshWriter::getVelocities(struct Mesh *mesh, std::vector<Imath::V3f> &vels) +{ + const int totverts = mesh->totvert; + + vels.clear(); + vels.resize(totverts); + + ModifierData *md = get_liquid_sim_modifier(m_settings.scene, m_object); + FluidsimModifierData *fmd = reinterpret_cast<FluidsimModifierData *>(md); + FluidsimSettings *fss = fmd->fss; + + if (fss->meshVelocities) { + float *mesh_vels = reinterpret_cast<float *>(fss->meshVelocities); + + for (int i = 0; i < totverts; i++) { + copy_yup_from_zup(vels[i].getValue(), mesh_vels); + mesh_vels += 3; + } + } + else { + std::fill(vels.begin(), vels.end(), Imath::V3f(0.0f)); + } +} + +void AbcGenericMeshWriter::getGeoGroups(struct Mesh *mesh, + std::map<std::string, std::vector<int32_t>> &geo_groups) +{ + const int num_poly = mesh->totpoly; + MPoly *polygons = mesh->mpoly; + + for (int i = 0; i < num_poly; i++) { + MPoly ¤t_poly = polygons[i]; + short mnr = current_poly.mat_nr; + + Material *mat = BKE_object_material_get(m_object, mnr + 1); + + if (!mat) { + continue; + } + + std::string name = get_id_name(&mat->id); + + if (geo_groups.find(name) == geo_groups.end()) { + std::vector<int32_t> faceArray; + geo_groups[name] = faceArray; + } + + geo_groups[name].push_back(i); + } + + if (geo_groups.size() == 0) { + Material *mat = BKE_object_material_get(m_object, 1); + + std::string name = (mat) ? get_id_name(&mat->id) : "default"; + + std::vector<int32_t> faceArray; + + for (int i = 0, e = mesh->totface; i < e; i++) { + faceArray.push_back(i); + } + + geo_groups[name] = faceArray; + } +} + +AbcMeshWriter::AbcMeshWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings) + : AbcGenericMeshWriter(ob, parent, time_sampling, settings) +{ +} + +AbcMeshWriter::~AbcMeshWriter() +{ +} + +Mesh *AbcMeshWriter::getEvaluatedMesh(Scene *scene_eval, + Object *ob_eval, + bool &UNUSED(r_needsfree)) +{ + return mesh_get_eval_final(m_settings.depsgraph, scene_eval, ob_eval, &CD_MASK_MESH); +} diff --git a/source/blender/io/alembic/intern/abc_writer_mesh.h b/source/blender/io/alembic/intern/abc_writer_mesh.h new file mode 100644 index 00000000000..9152a370e4f --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_mesh.h @@ -0,0 +1,91 @@ +/* + * 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 + */ + +#ifndef __ABC_WRITER_MESH_H__ +#define __ABC_WRITER_MESH_H__ + +#include "abc_customdata.h" +#include "abc_writer_object.h" + +struct Mesh; +struct ModifierData; + +/* Writer for Alembic meshes. Does not assume the object is a mesh object. */ +class AbcGenericMeshWriter : public AbcObjectWriter { + protected: + Alembic::AbcGeom::OPolyMeshSchema m_mesh_schema; + Alembic::AbcGeom::OPolyMeshSchema::Sample m_mesh_sample; + + Alembic::AbcGeom::OSubDSchema m_subdiv_schema; + Alembic::AbcGeom::OSubDSchema::Sample m_subdiv_sample; + + Alembic::Abc::OArrayProperty m_mat_indices; + + bool m_is_animated; + ModifierData *m_subsurf_mod; + + CDStreamConfig m_custom_data_config; + + bool m_is_liquid; + bool m_is_subd; + + public: + AbcGenericMeshWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings); + + ~AbcGenericMeshWriter(); + void setIsAnimated(bool is_animated); + + protected: + virtual void do_write(); + virtual bool isAnimated() const; + virtual Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree) = 0; + virtual void freeEvaluatedMesh(struct Mesh *mesh); + + Mesh *getFinalMesh(bool &r_needsfree); + + void writeMesh(struct Mesh *mesh); + void writeSubD(struct Mesh *mesh); + + void writeArbGeoParams(struct Mesh *mesh); + void getGeoGroups(struct Mesh *mesh, std::map<std::string, std::vector<int32_t>> &geoGroups); + + /* fluid surfaces support */ + void getVelocities(struct Mesh *mesh, std::vector<Imath::V3f> &vels); + + template<typename Schema> void writeFaceSets(struct Mesh *mesh, Schema &schema); +}; + +class AbcMeshWriter : public AbcGenericMeshWriter { + public: + AbcMeshWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings); + + ~AbcMeshWriter(); + + protected: + virtual Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree) override; +}; + +#endif /* __ABC_WRITER_MESH_H__ */ diff --git a/source/blender/io/alembic/intern/abc_writer_nurbs.cc b/source/blender/io/alembic/intern/abc_writer_nurbs.cc new file mode 100644 index 00000000000..9796eaf54c3 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_nurbs.cc @@ -0,0 +1,172 @@ +/* + * 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_writer_nurbs.h" +#include "abc_writer_transform.h" +#include "abc_util.h" + +extern "C" { +#include "DNA_curve_types.h" +#include "DNA_object_types.h" + +#include "BLI_listbase.h" + +#include "BKE_curve.h" +} + +using Alembic::AbcGeom::FloatArraySample; +using Alembic::AbcGeom::OBoolProperty; +using Alembic::AbcGeom::OCompoundProperty; +using Alembic::AbcGeom::ONuPatch; +using Alembic::AbcGeom::ONuPatchSchema; + +AbcNurbsWriter::AbcNurbsWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings) + : AbcObjectWriter(ob, time_sampling, settings, parent) +{ + m_is_animated = isAnimated(); + + /* if the object is static, use the default static time sampling */ + if (!m_is_animated) { + m_time_sampling = 0; + } + + Curve *curve = static_cast<Curve *>(m_object->data); + size_t numNurbs = BLI_listbase_count(&curve->nurb); + + for (size_t i = 0; i < numNurbs; i++) { + std::stringstream str; + str << m_name << '_' << i; + + while (parent->alembicXform().getChildHeader(str.str())) { + str << "_"; + } + + ONuPatch nurbs(parent->alembicXform(), str.str().c_str(), m_time_sampling); + m_nurbs_schema.push_back(nurbs.getSchema()); + } +} + +bool AbcNurbsWriter::isAnimated() const +{ + /* check if object has shape keys */ + Curve *cu = static_cast<Curve *>(m_object->data); + return (cu->key != NULL); +} + +static void get_knots(std::vector<float> &knots, const int num_knots, float *nu_knots) +{ + if (num_knots <= 1) { + return; + } + + /* Add an extra knot at the beginning and end of the array since most apps + * require/expect them. */ + knots.reserve(num_knots + 2); + + knots.push_back(0.0f); + + for (int i = 0; i < num_knots; i++) { + knots.push_back(nu_knots[i]); + } + + knots[0] = 2.0f * knots[1] - knots[2]; + knots.push_back(2.0f * knots[num_knots] - knots[num_knots - 1]); +} + +void AbcNurbsWriter::do_write() +{ + /* we have already stored a sample for this object. */ + if (!m_first_frame && !m_is_animated) { + return; + } + + if (!ELEM(m_object->type, OB_SURF, OB_CURVE)) { + return; + } + + Curve *curve = static_cast<Curve *>(m_object->data); + ListBase *nulb; + + if (m_object->runtime.curve_cache->deformed_nurbs.first != NULL) { + nulb = &m_object->runtime.curve_cache->deformed_nurbs; + } + else { + nulb = BKE_curve_nurbs_get(curve); + } + + size_t count = 0; + for (Nurb *nu = static_cast<Nurb *>(nulb->first); nu; nu = nu->next, count++) { + std::vector<float> knotsU; + get_knots(knotsU, KNOTSU(nu), nu->knotsu); + + std::vector<float> knotsV; + get_knots(knotsV, KNOTSV(nu), nu->knotsv); + + const int size = nu->pntsu * nu->pntsv; + std::vector<Imath::V3f> positions(size); + std::vector<float> weights(size); + + const BPoint *bp = nu->bp; + + for (int i = 0; i < size; i++, bp++) { + copy_yup_from_zup(positions[i].getValue(), bp->vec); + weights[i] = bp->vec[3]; + } + + ONuPatchSchema::Sample sample; + sample.setUOrder(nu->orderu + 1); + sample.setVOrder(nu->orderv + 1); + sample.setPositions(positions); + sample.setPositionWeights(weights); + sample.setUKnot(FloatArraySample(knotsU)); + sample.setVKnot(FloatArraySample(knotsV)); + sample.setNu(nu->pntsu); + sample.setNv(nu->pntsv); + + /* TODO(kevin): to accommodate other software we should duplicate control + * points to indicate that a NURBS is cyclic. */ + OCompoundProperty user_props = m_nurbs_schema[count].getUserProperties(); + + if ((nu->flagu & CU_NURB_ENDPOINT) != 0) { + OBoolProperty prop(user_props, "endpoint_u"); + prop.set(true); + } + + if ((nu->flagv & CU_NURB_ENDPOINT) != 0) { + OBoolProperty prop(user_props, "endpoint_v"); + prop.set(true); + } + + if ((nu->flagu & CU_NURB_CYCLIC) != 0) { + OBoolProperty prop(user_props, "cyclic_u"); + prop.set(true); + } + + if ((nu->flagv & CU_NURB_CYCLIC) != 0) { + OBoolProperty prop(user_props, "cyclic_v"); + prop.set(true); + } + + m_nurbs_schema[count].set(sample); + } +} diff --git a/source/blender/io/alembic/intern/abc_writer_nurbs.h b/source/blender/io/alembic/intern/abc_writer_nurbs.h new file mode 100644 index 00000000000..c6a3c399b66 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_nurbs.h @@ -0,0 +1,42 @@ +/* + * 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 + */ + +#ifndef __ABC_WRITER_NURBS_H__ +#define __ABC_WRITER_NURBS_H__ + +#include "abc_writer_object.h" + +class AbcNurbsWriter : public AbcObjectWriter { + std::vector<Alembic::AbcGeom::ONuPatchSchema> m_nurbs_schema; + bool m_is_animated; + + public: + AbcNurbsWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings); + + private: + virtual void do_write(); + + bool isAnimated() const; +}; + +#endif /* __ABC_WRITER_NURBS_H__ */ diff --git a/source/blender/io/alembic/intern/abc_writer_object.cc b/source/blender/io/alembic/intern/abc_writer_object.cc new file mode 100644 index 00000000000..75dc93bd08e --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_object.cc @@ -0,0 +1,79 @@ +/* + * 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_writer_object.h" + +extern "C" { +#include "DNA_object_types.h" + +#include "BKE_object.h" +} + +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; +} diff --git a/source/blender/io/alembic/intern/abc_writer_object.h b/source/blender/io/alembic/intern/abc_writer_object.h new file mode 100644 index 00000000000..c3511566372 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_object.h @@ -0,0 +1,71 @@ +/* + * 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 + */ + +#ifndef __ABC_WRITER_OBJECT_H__ +#define __ABC_WRITER_OBJECT_H__ + +#include <Alembic/Abc/All.h> +#include <Alembic/AbcGeom/All.h> + +#include "abc_exporter.h" + +extern "C" { +#include "DNA_ID.h" +} + +class AbcTransformWriter; + +struct Main; +struct Object; + +class AbcObjectWriter { + protected: + Object *m_object; + ExportSettings &m_settings; + + uint32_t m_time_sampling; + + Imath::Box3d m_bounds; + std::vector<AbcObjectWriter *> m_children; + + std::vector<std::pair<std::string, IDProperty *>> m_props; + + bool m_first_frame; + std::string m_name; + + public: + AbcObjectWriter(Object *ob, + uint32_t time_sampling, + ExportSettings &settings, + AbcObjectWriter *parent = NULL); + + virtual ~AbcObjectWriter(); + + void addChild(AbcObjectWriter *child); + + virtual Imath::Box3d bounds(); + + void write(); + + private: + virtual void do_write() = 0; +}; + +#endif /* __ABC_WRITER_OBJECT_H__ */ diff --git a/source/blender/io/alembic/intern/abc_writer_points.cc b/source/blender/io/alembic/intern/abc_writer_points.cc new file mode 100644 index 00000000000..cc4abe8ec4b --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_points.cc @@ -0,0 +1,123 @@ +/* + * 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. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_writer_points.h" +#include "abc_writer_mesh.h" +#include "abc_writer_transform.h" +#include "abc_util.h" + +extern "C" { +#include "DNA_object_types.h" +#include "DNA_particle_types.h" + +#include "BKE_lattice.h" +#include "BKE_particle.h" + +#include "BLI_math.h" + +#include "DEG_depsgraph_query.h" +} + +using Alembic::AbcGeom::kVertexScope; +using Alembic::AbcGeom::OPoints; +using Alembic::AbcGeom::OPointsSchema; + +/* ************************************************************************** */ + +AbcPointsWriter::AbcPointsWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings, + ParticleSystem *psys) + : AbcObjectWriter(ob, time_sampling, settings, parent) +{ + m_psys = psys; + + OPoints points(parent->alembicXform(), psys->name, m_time_sampling); + m_schema = points.getSchema(); +} + +void AbcPointsWriter::do_write() +{ + if (!m_psys) { + return; + } + + std::vector<Imath::V3f> points; + std::vector<Imath::V3f> velocities; + std::vector<float> widths; + std::vector<uint64_t> ids; + + ParticleKey state; + + ParticleSimulationData sim; + sim.depsgraph = m_settings.depsgraph; + sim.scene = m_settings.scene; + sim.ob = m_object; + sim.psys = m_psys; + + m_psys->lattice_deform_data = psys_create_lattice_deform_data(&sim); + + uint64_t index = 0; + for (int p = 0; p < m_psys->totpart; p++) { + float pos[3], vel[3]; + + if (m_psys->particles[p].flag & (PARS_NO_DISP | PARS_UNEXIST)) { + continue; + } + + state.time = DEG_get_ctime(m_settings.depsgraph); + + if (psys_get_particle_state(&sim, p, &state, 0) == 0) { + continue; + } + + /* location */ + mul_v3_m4v3(pos, m_object->imat, state.co); + + /* velocity */ + sub_v3_v3v3(vel, state.co, m_psys->particles[p].prev_state.co); + + /* Convert Z-up to Y-up. */ + points.push_back(Imath::V3f(pos[0], pos[2], -pos[1])); + velocities.push_back(Imath::V3f(vel[0], vel[2], -vel[1])); + widths.push_back(m_psys->particles[p].size); + ids.push_back(index++); + } + + if (m_psys->lattice_deform_data) { + end_latt_deform(m_psys->lattice_deform_data); + m_psys->lattice_deform_data = NULL; + } + + Alembic::Abc::P3fArraySample psample(points); + Alembic::Abc::UInt64ArraySample idsample(ids); + Alembic::Abc::V3fArraySample vsample(velocities); + Alembic::Abc::FloatArraySample wsample_array(widths); + Alembic::AbcGeom::OFloatGeomParam::Sample wsample(wsample_array, kVertexScope); + + m_sample = OPointsSchema::Sample(psample, idsample, vsample, wsample); + m_sample.setSelfBounds(bounds()); + + m_schema.set(m_sample); +} diff --git a/source/blender/io/alembic/intern/abc_writer_points.h b/source/blender/io/alembic/intern/abc_writer_points.h new file mode 100644 index 00000000000..77dd10c4b26 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_points.h @@ -0,0 +1,49 @@ +/* + * 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. + */ + +/** \file + * \ingroup balembic + */ + +#ifndef __ABC_WRITER_POINTS_H__ +#define __ABC_WRITER_POINTS_H__ + +#include "abc_writer_object.h" +#include "abc_customdata.h" + +struct ParticleSystem; + +/* ************************************************************************** */ + +class AbcPointsWriter : public AbcObjectWriter { + Alembic::AbcGeom::OPointsSchema m_schema; + Alembic::AbcGeom::OPointsSchema::Sample m_sample; + ParticleSystem *m_psys; + + public: + AbcPointsWriter(Object *ob, + AbcTransformWriter *parent, + uint32_t time_sampling, + ExportSettings &settings, + ParticleSystem *psys); + + void do_write(); +}; + +#endif /* __ABC_WRITER_POINTS_H__ */ diff --git a/source/blender/io/alembic/intern/abc_writer_transform.cc b/source/blender/io/alembic/intern/abc_writer_transform.cc new file mode 100644 index 00000000000..d7bcc46d96f --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_transform.cc @@ -0,0 +1,121 @@ +/* + * 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_writer_transform.h" +#include "abc_util.h" + +#include <OpenEXR/ImathBoxAlgo.h> + +extern "C" { +#include "DNA_object_types.h" + +#include "BLI_math.h" + +#include "DEG_depsgraph_query.h" +} + +using Alembic::AbcGeom::OObject; +using Alembic::AbcGeom::OXform; + +AbcTransformWriter::AbcTransformWriter(Object *ob, + const OObject &abc_parent, + AbcTransformWriter *parent, + unsigned int time_sampling, + ExportSettings &settings) + : AbcObjectWriter(ob, time_sampling, settings, parent), m_proxy_from(NULL) +{ + m_is_animated = hasAnimation(m_object); + + if (!m_is_animated) { + time_sampling = 0; + } + + m_xform = OXform(abc_parent, get_id_name(m_object), time_sampling); + m_schema = m_xform.getSchema(); + + /* Blender objects can't have a parent without inheriting the transform. */ + m_inherits_xform = parent != NULL; +} + +void AbcTransformWriter::do_write() +{ + Object *ob_eval = DEG_get_evaluated_object(m_settings.depsgraph, m_object); + + if (m_first_frame) { + m_visibility = Alembic::AbcGeom::CreateVisibilityProperty( + m_xform, m_xform.getSchema().getTimeSampling()); + } + + m_visibility.set(!(ob_eval->restrictflag & OB_RESTRICT_VIEWPORT)); + + if (!m_first_frame && !m_is_animated) { + return; + } + + float yup_mat[4][4]; + create_transform_matrix( + ob_eval, yup_mat, m_inherits_xform ? ABC_MATRIX_LOCAL : ABC_MATRIX_WORLD, m_proxy_from); + + /* If the parent is a camera, undo its to-Maya rotation (see below). */ + bool is_root_object = !m_inherits_xform || ob_eval->parent == nullptr; + if (!is_root_object && ob_eval->parent->type == OB_CAMERA) { + float rot_mat[4][4]; + axis_angle_to_mat4_single(rot_mat, 'X', M_PI_2); + mul_m4_m4m4(yup_mat, rot_mat, yup_mat); + } + + /* If the object is a camera, apply an extra rotation to Maya camera orientation. */ + if (ob_eval->type == OB_CAMERA) { + float rot_mat[4][4]; + axis_angle_to_mat4_single(rot_mat, 'X', -M_PI_2); + mul_m4_m4m4(yup_mat, yup_mat, rot_mat); + } + + if (is_root_object) { + /* Only apply scaling to root objects, parenting will propagate it. */ + float scale_mat[4][4]; + scale_m4_fl(scale_mat, m_settings.global_scale); + scale_mat[3][3] = m_settings.global_scale; /* also scale translation */ + mul_m4_m4m4(yup_mat, yup_mat, scale_mat); + yup_mat[3][3] /= m_settings.global_scale; /* normalise the homogeneous component */ + } + + m_matrix = convert_matrix_datatype(yup_mat); + m_sample.setMatrix(m_matrix); + m_sample.setInheritsXforms(m_inherits_xform); + m_schema.set(m_sample); +} + +Imath::Box3d AbcTransformWriter::bounds() +{ + Imath::Box3d bounds; + + for (int i = 0; i < m_children.size(); i++) { + Imath::Box3d box(m_children[i]->bounds()); + bounds.extendBy(box); + } + + return Imath::transform(bounds, m_matrix); +} + +bool AbcTransformWriter::hasAnimation(Object * /*ob*/) const +{ + return true; +} diff --git a/source/blender/io/alembic/intern/abc_writer_transform.h b/source/blender/io/alembic/intern/abc_writer_transform.h new file mode 100644 index 00000000000..4397b220761 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_writer_transform.h @@ -0,0 +1,60 @@ +/* + * 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 + */ + +#ifndef __ABC_WRITER_TRANSFORM_H__ +#define __ABC_WRITER_TRANSFORM_H__ + +#include "abc_writer_object.h" + +#include <Alembic/AbcGeom/All.h> + +class AbcTransformWriter : public AbcObjectWriter { + Alembic::AbcGeom::OXform m_xform; + Alembic::AbcGeom::OXformSchema m_schema; + Alembic::AbcGeom::XformSample m_sample; + Alembic::AbcGeom::OVisibilityProperty m_visibility; + Alembic::Abc::M44d m_matrix; + + bool m_is_animated; + bool m_inherits_xform; + + public: + Object *m_proxy_from; + + public: + AbcTransformWriter(Object *ob, + const Alembic::AbcGeom::OObject &abc_parent, + AbcTransformWriter *parent, + unsigned int time_sampling, + ExportSettings &settings); + + Alembic::AbcGeom::OXform &alembicXform() + { + return m_xform; + } + virtual Imath::Box3d bounds(); + + private: + virtual void do_write(); + + bool hasAnimation(Object *ob) const; +}; + +#endif /* __ABC_WRITER_TRANSFORM_H__ */ diff --git a/source/blender/io/alembic/intern/alembic_capi.cc b/source/blender/io/alembic/intern/alembic_capi.cc new file mode 100644 index 00000000000..c6f9e284d53 --- /dev/null +++ b/source/blender/io/alembic/intern/alembic_capi.cc @@ -0,0 +1,1052 @@ +/* + * 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_alembic.h" + +#include <Alembic/AbcMaterial/IMaterial.h> + +#include "abc_reader_archive.h" +#include "abc_reader_camera.h" +#include "abc_reader_curves.h" +#include "abc_reader_mesh.h" +#include "abc_reader_nurbs.h" +#include "abc_reader_points.h" +#include "abc_reader_transform.h" +#include "abc_util.h" +#include "abc_writer_camera.h" +#include "abc_writer_curves.h" +#include "abc_writer_hair.h" +#include "abc_writer_mesh.h" +#include "abc_writer_nurbs.h" +#include "abc_writer_points.h" +#include "abc_writer_transform.h" + +#include "MEM_guardedalloc.h" + +extern "C" { +#include "DNA_cachefile_types.h" +#include "DNA_curve_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BKE_cachefile.h" +#include "BKE_context.h" +#include "BKE_curve.h" +#include "BKE_global.h" +#include "BKE_layer.h" +#include "BKE_lib_id.h" +#include "BKE_scene.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" + +/* SpaceType struct has a member called 'new' which obviously conflicts with C++ + * so temporarily redefining the new keyword to make it compile. */ +#define new extern_new +#include "BKE_screen.h" +#undef new + +#include "BLI_fileops.h" +#include "BLI_ghash.h" +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_path_util.h" +#include "BLI_string.h" + +#include "WM_api.h" +#include "WM_types.h" +} + +using Alembic::Abc::Int32ArraySamplePtr; +using Alembic::Abc::ObjectHeader; + +using Alembic::AbcGeom::kWrapExisting; +using Alembic::AbcGeom::MetaData; +using Alembic::AbcGeom::P3fArraySamplePtr; + +using Alembic::AbcGeom::ICamera; +using Alembic::AbcGeom::ICompoundProperty; +using Alembic::AbcGeom::ICurves; +using Alembic::AbcGeom::ICurvesSchema; +using Alembic::AbcGeom::IFaceSet; +using Alembic::AbcGeom::ILight; +using Alembic::AbcGeom::IN3fArrayProperty; +using Alembic::AbcGeom::IN3fGeomParam; +using Alembic::AbcGeom::INuPatch; +using Alembic::AbcGeom::IObject; +using Alembic::AbcGeom::IPoints; +using Alembic::AbcGeom::IPointsSchema; +using Alembic::AbcGeom::IPolyMesh; +using Alembic::AbcGeom::IPolyMeshSchema; +using Alembic::AbcGeom::ISampleSelector; +using Alembic::AbcGeom::ISubD; +using Alembic::AbcGeom::IV2fGeomParam; +using Alembic::AbcGeom::IXform; +using Alembic::AbcGeom::IXformSchema; +using Alembic::AbcGeom::N3fArraySamplePtr; +using Alembic::AbcGeom::V3fArraySamplePtr; +using Alembic::AbcGeom::XformSample; + +using Alembic::AbcMaterial::IMaterial; + +struct AbcArchiveHandle { + int unused; +}; + +ABC_INLINE ArchiveReader *archive_from_handle(AbcArchiveHandle *handle) +{ + return reinterpret_cast<ArchiveReader *>(handle); +} + +ABC_INLINE AbcArchiveHandle *handle_from_archive(ArchiveReader *archive) +{ + return reinterpret_cast<AbcArchiveHandle *>(archive); +} + +//#define USE_NURBS + +/* NOTE: this function is similar to visit_objects below, need to keep them in + * sync. */ +static bool gather_objects_paths(const IObject &object, ListBase *object_paths) +{ + if (!object.valid()) { + return false; + } + + size_t children_claiming_this_object = 0; + size_t num_children = object.getNumChildren(); + + 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 = object.getMetaData(); + bool get_path = false; + bool parent_is_part_of_this_object = false; + + 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 { + get_path = children_claiming_this_object == 0; + } + + /* 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; + } + + if (get_path) { + void *abc_path_void = MEM_callocN(sizeof(AlembicObjectPath), "AlembicObjectPath"); + AlembicObjectPath *abc_path = static_cast<AlembicObjectPath *>(abc_path_void); + + BLI_strncpy(abc_path->path, object.getFullName().c_str(), sizeof(abc_path->path)); + BLI_addtail(object_paths, abc_path); + } + + return parent_is_part_of_this_object; +} + +AbcArchiveHandle *ABC_create_handle(struct Main *bmain, + const char *filename, + ListBase *object_paths) +{ + ArchiveReader *archive = new ArchiveReader(bmain, filename); + + if (!archive->valid()) { + delete archive; + return NULL; + } + + if (object_paths) { + gather_objects_paths(archive->getTop(), object_paths); + } + + return handle_from_archive(archive); +} + +void ABC_free_handle(AbcArchiveHandle *handle) +{ + delete archive_from_handle(handle); +} + +int ABC_get_version() +{ + return ALEMBIC_LIBRARY_VERSION; +} + +static void find_iobject(const IObject &object, IObject &ret, const std::string &path) +{ + if (!object.valid()) { + return; + } + + std::vector<std::string> tokens; + split(path, '/', tokens); + + IObject tmp = object; + + std::vector<std::string>::iterator iter; + for (iter = tokens.begin(); iter != tokens.end(); ++iter) { + IObject child = tmp.getChild(*iter); + tmp = child; + } + + ret = tmp; +} + +struct ExportJobData { + ViewLayer *view_layer; + Main *bmain; + wmWindowManager *wm; + + char filename[1024]; + ExportSettings settings; + + short *stop; + short *do_update; + float *progress; + + bool was_canceled; + bool export_ok; +}; + +static void export_startjob(void *customdata, short *stop, short *do_update, float *progress) +{ + ExportJobData *data = static_cast<ExportJobData *>(customdata); + + data->stop = stop; + data->do_update = do_update; + data->progress = progress; + + /* XXX annoying hack: needed to prevent data corruption when changing + * scene frame in separate threads + */ + G.is_rendering = true; + WM_set_locked_interface(data->wm, true); + G.is_break = false; + + DEG_graph_build_from_view_layer( + data->settings.depsgraph, data->bmain, data->settings.scene, data->view_layer); + BKE_scene_graph_update_tagged(data->settings.depsgraph, data->bmain); + + try { + AbcExporter exporter(data->bmain, data->filename, data->settings); + + Scene *scene = data->settings.scene; /* for the CFRA macro */ + const int orig_frame = CFRA; + + data->was_canceled = false; + exporter(do_update, progress, &data->was_canceled); + + if (CFRA != orig_frame) { + CFRA = orig_frame; + + BKE_scene_graph_update_for_newframe(data->settings.depsgraph, data->bmain); + } + + data->export_ok = !data->was_canceled; + } + catch (const std::exception &e) { + ABC_LOG(data->settings.logger) << "Abc Export error: " << e.what() << '\n'; + } + catch (...) { + ABC_LOG(data->settings.logger) << "Abc Export: unknown error...\n"; + } +} + +static void export_endjob(void *customdata) +{ + ExportJobData *data = static_cast<ExportJobData *>(customdata); + + DEG_graph_free(data->settings.depsgraph); + + if (data->was_canceled && BLI_exists(data->filename)) { + BLI_delete(data->filename, false, false); + } + + std::string log = data->settings.logger.str(); + if (!log.empty()) { + std::cerr << log; + WM_report(RPT_ERROR, "Errors occurred during the export, look in the console to know more..."); + } + + G.is_rendering = false; + WM_set_locked_interface(data->wm, false); +} + +bool ABC_export(Scene *scene, + bContext *C, + const char *filepath, + const struct AlembicExportParams *params, + bool as_background_job) +{ + ExportJobData *job = static_cast<ExportJobData *>( + MEM_mallocN(sizeof(ExportJobData), "ExportJobData")); + + job->view_layer = CTX_data_view_layer(C); + job->bmain = CTX_data_main(C); + job->wm = CTX_wm_manager(C); + job->export_ok = false; + BLI_strncpy(job->filename, filepath, 1024); + + /* Alright, alright, alright.... + * + * ExportJobData contains an ExportSettings containing a SimpleLogger. + * + * Since ExportJobData is a C-style struct dynamically allocated with + * MEM_mallocN (see above), its constructor is never called, therefore the + * ExportSettings constructor is not called which implies that the + * SimpleLogger one is not called either. SimpleLogger in turn does not call + * the constructor of its data members which ultimately means that its + * std::ostringstream member has a NULL pointer. To be able to properly use + * the stream's operator<<, the pointer needs to be set, therefore we have + * to properly construct everything. And this is done using the placement + * new operator as here below. It seems hackish, but I'm too lazy to + * do bigger refactor and maybe there is a better way which does not involve + * hardcore refactoring. */ + new (&job->settings) ExportSettings(); + job->settings.scene = scene; + job->settings.depsgraph = DEG_graph_new(job->bmain, scene, job->view_layer, DAG_EVAL_RENDER); + + /* TODO(Sybren): for now we only export the active scene layer. + * Later in the 2.8 development process this may be replaced by using + * a specific collection for Alembic I/O, which can then be toggled + * between "real" objects and cached Alembic files. */ + job->settings.view_layer = job->view_layer; + + job->settings.frame_start = params->frame_start; + job->settings.frame_end = params->frame_end; + job->settings.frame_samples_xform = params->frame_samples_xform; + job->settings.frame_samples_shape = params->frame_samples_shape; + job->settings.shutter_open = params->shutter_open; + job->settings.shutter_close = params->shutter_close; + + /* TODO(Sybren): For now this is ignored, until we can get selection + * detection working through Base pointers (instead of ob->flags). */ + job->settings.selected_only = params->selected_only; + + job->settings.export_face_sets = params->face_sets; + job->settings.export_normals = params->normals; + job->settings.export_uvs = params->uvs; + job->settings.export_vcols = params->vcolors; + job->settings.export_hair = params->export_hair; + job->settings.export_particles = params->export_particles; + job->settings.apply_subdiv = params->apply_subdiv; + job->settings.curves_as_mesh = params->curves_as_mesh; + job->settings.flatten_hierarchy = params->flatten_hierarchy; + + /* TODO(Sybren): visible_layer & renderable only is ignored for now, + * to be replaced with collections later in the 2.8 dev process + * (also see note above). */ + job->settings.visible_objects_only = params->visible_objects_only; + job->settings.renderable_only = params->renderable_only; + + job->settings.use_subdiv_schema = params->use_subdiv_schema; + job->settings.export_ogawa = (params->compression_type == ABC_ARCHIVE_OGAWA); + job->settings.pack_uv = params->packuv; + job->settings.global_scale = params->global_scale; + job->settings.triangulate = params->triangulate; + job->settings.quad_method = params->quad_method; + job->settings.ngon_method = params->ngon_method; + + if (job->settings.frame_start > job->settings.frame_end) { + std::swap(job->settings.frame_start, job->settings.frame_end); + } + + bool export_ok = false; + if (as_background_job) { + wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C), + CTX_wm_window(C), + job->settings.scene, + "Alembic Export", + WM_JOB_PROGRESS, + WM_JOB_TYPE_ALEMBIC); + + /* setup job */ + WM_jobs_customdata_set(wm_job, job, MEM_freeN); + WM_jobs_timer(wm_job, 0.1, NC_SCENE | ND_FRAME, NC_SCENE | ND_FRAME); + WM_jobs_callbacks(wm_job, export_startjob, NULL, NULL, export_endjob); + + WM_jobs_start(CTX_wm_manager(C), wm_job); + } + else { + /* Fake a job context, so that we don't need NULL pointer checks while exporting. */ + short stop = 0, do_update = 0; + float progress = 0.f; + + export_startjob(job, &stop, &do_update, &progress); + export_endjob(job); + export_ok = job->export_ok; + + MEM_freeN(job); + } + + return export_ok; +} + +/* ********************** Import file ********************** */ + +/** + * 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()) { + std::cerr << " - " << full_name << ": object is invalid, skipping it and all its children.\n"; + return std::make_pair(false, static_cast<AbcObjectReader *>(NULL)); + } + + /* 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); + + /* 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); + } + } + + children_claiming_this_object += child_claims_this_object ? 1 : 0; + } + BLI_assert(children_claiming_this_object == claiming_child_readers.size()); + + AbcObjectReader *reader = NULL; + const MetaData &md = object.getMetaData(); + bool parent_is_part_of_this_object = false; + + if (!object.getParent()) { + /* The root itself is not an object we should import. */ + } + else if (IXform::matches(md)) { + bool create_empty; + + /* 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. */ + + /* 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 { + create_empty = claiming_child_readers.empty(); + } + + if (create_empty) { + reader = new AbcEmptyReader(object, settings); + } + } + 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(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(), sizeof(abc_path->path)); + 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) { + for (AbcObjectReader *child_reader : nonclaiming_child_readers) { + child_reader->parent_reader = reader; + } + for (AbcObjectReader *child_reader : assign_as_parent) { + child_reader->parent_reader = reader; + } + } + } + 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]; + for (AbcObjectReader *child_reader : nonclaiming_child_readers) { + child_reader->parent_reader = claiming_child; + } + for (AbcObjectReader *child_reader : assign_as_parent) { + child_reader->parent_reader = claiming_child; + } + /* Claiming children should have our parent set as their parent. */ + for (AbcObjectReader *child_reader : claiming_child_readers) { + r_assign_as_parent.push_back(child_reader); + } + } + else { + /* 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. */ + for (AbcObjectReader *child_reader : claiming_child_readers) { + r_assign_as_parent.push_back(child_reader); + } + for (AbcObjectReader *child_reader : nonclaiming_child_readers) { + r_assign_as_parent.push_back(child_reader); + } + for (AbcObjectReader *child_reader : assign_as_parent) { + r_assign_as_parent.push_back(child_reader); + } + } + } + + return std::make_pair(parent_is_part_of_this_object, reader); +} + +enum { + ABC_NO_ERROR = 0, + ABC_ARCHIVE_FAIL, + ABC_UNSUPPORTED_HDF5, +}; + +struct ImportJobData { + Main *bmain; + Scene *scene; + ViewLayer *view_layer; + wmWindowManager *wm; + + char filename[1024]; + ImportSettings settings; + + ArchiveReader *archive; + std::vector<AbcObjectReader *> readers; + + short *stop; + short *do_update; + float *progress; + + char error_code; + bool was_cancelled; + bool import_ok; +}; + +static void import_startjob(void *user_data, short *stop, short *do_update, float *progress) +{ + SCOPE_TIMER("Alembic import, objects reading and creation"); + + ImportJobData *data = static_cast<ImportJobData *>(user_data); + + data->stop = stop; + data->do_update = do_update; + data->progress = progress; + + WM_set_locked_interface(data->wm, true); + + ArchiveReader *archive = new ArchiveReader(data->bmain, data->filename); + + if (!archive->valid()) { +#ifndef WITH_ALEMBIC_HDF5 + data->error_code = archive->is_hdf5() ? ABC_UNSUPPORTED_HDF5 : ABC_ARCHIVE_FAIL; +#else + data->error_code = ABC_ARCHIVE_FAIL; +#endif + delete archive; + return; + } + + CacheFile *cache_file = static_cast<CacheFile *>( + BKE_cachefile_add(data->bmain, BLI_path_basename(data->filename))); + + /* Decrement the ID ref-count because it is going to be incremented for each + * modifier and constraint that it will be attached to, so since currently + * it is not used by anyone, its use count will off by one. */ + id_us_min(&cache_file->id); + + cache_file->is_sequence = data->settings.is_sequence; + cache_file->scale = data->settings.scale; + STRNCPY(cache_file->filepath, data->filename); + + data->archive = archive; + data->settings.cache_file = cache_file; + + *data->do_update = true; + *data->progress = 0.05f; + + /* Parse Alembic Archive. */ + AbcObjectReader::ptr_vector assign_as_parent; + visit_object(archive->getTop(), data->readers, data->settings, assign_as_parent); + + /* There shouldn't be any orphans. */ + BLI_assert(assign_as_parent.size() == 0); + + if (G.is_break) { + data->was_cancelled = true; + return; + } + + *data->do_update = true; + *data->progress = 0.1f; + + /* Create objects and set scene frame range. */ + + const float size = static_cast<float>(data->readers.size()); + size_t i = 0; + + chrono_t min_time = std::numeric_limits<chrono_t>::max(); + chrono_t max_time = std::numeric_limits<chrono_t>::min(); + + ISampleSelector sample_sel(0.0f); + std::vector<AbcObjectReader *>::iterator iter; + for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { + AbcObjectReader *reader = *iter; + + if (reader->valid()) { + reader->readObjectData(data->bmain, sample_sel); + + 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.3f * (++i / size); + *data->do_update = true; + + if (G.is_break) { + data->was_cancelled = true; + return; + } + } + + if (data->settings.set_frame_range) { + Scene *scene = data->scene; + + if (data->settings.is_sequence) { + SFRA = data->settings.sequence_offset; + EFRA = SFRA + (data->settings.sequence_len - 1); + CFRA = SFRA; + } + else if (min_time < max_time) { + SFRA = static_cast<int>(round(min_time * FPS)); + EFRA = static_cast<int>(round(max_time * FPS)); + CFRA = SFRA; + } + } + + /* Setup parenthood. */ + for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { + const AbcObjectReader *reader = *iter; + const AbcObjectReader *parent_reader = reader->parent_reader; + Object *ob = reader->object(); + + if (parent_reader == NULL || !reader->inherits_xform()) { + ob->parent = NULL; + } + 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; + + if (G.is_break) { + data->was_cancelled = true; + return; + } + } +} + +static void import_endjob(void *user_data) +{ + SCOPE_TIMER("Alembic import, cleanup"); + + ImportJobData *data = static_cast<ImportJobData *>(user_data); + + std::vector<AbcObjectReader *>::iterator iter; + + /* Delete objects on cancellation. */ + if (data->was_cancelled) { + for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { + Object *ob = (*iter)->object(); + + /* It's possible that cancellation occurred between the creation of + * the reader and the creation of the Blender object. */ + if (ob == NULL) { + continue; + } + + BKE_id_free_us(data->bmain, ob); + } + } + else { + /* Add object to scene. */ + Base *base; + LayerCollection *lc; + ViewLayer *view_layer = data->view_layer; + + BKE_view_layer_base_deselect_all(view_layer); + + lc = BKE_layer_collection_get_active(view_layer); + + for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { + Object *ob = (*iter)->object(); + + BKE_collection_object_add(data->bmain, lc->collection, ob); + + base = BKE_view_layer_base_find(view_layer, ob); + /* TODO: is setting active needed? */ + BKE_view_layer_base_select_and_set_active(view_layer, base); + + DEG_id_tag_update(&lc->collection->id, ID_RECALC_COPY_ON_WRITE); + DEG_id_tag_update_ex(data->bmain, + &ob->id, + ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION | + ID_RECALC_BASE_FLAGS); + } + + DEG_id_tag_update(&data->scene->id, ID_RECALC_BASE_FLAGS); + DEG_relations_tag_update(data->bmain); + } + + for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { + AbcObjectReader *reader = *iter; + reader->decref(); + + if (reader->refcount() == 0) { + delete reader; + } + } + + WM_set_locked_interface(data->wm, false); + + switch (data->error_code) { + default: + case ABC_NO_ERROR: + data->import_ok = !data->was_cancelled; + break; + case ABC_ARCHIVE_FAIL: + WM_report(RPT_ERROR, "Could not open Alembic archive for reading! See console for detail."); + break; + case ABC_UNSUPPORTED_HDF5: + WM_report(RPT_ERROR, "Alembic archive in obsolete HDF5 format is not supported."); + break; + } + + WM_main_add_notifier(NC_SCENE | ND_FRAME, data->scene); +} + +static void import_freejob(void *user_data) +{ + ImportJobData *data = static_cast<ImportJobData *>(user_data); + delete data->archive; + delete data; +} + +bool ABC_import(bContext *C, + const char *filepath, + float scale, + bool is_sequence, + bool set_frame_range, + int sequence_len, + int offset, + bool validate_meshes, + bool as_background_job) +{ + /* Using new here since MEM_* funcs do not call ctor to properly initialize + * data. */ + ImportJobData *job = new ImportJobData(); + job->bmain = CTX_data_main(C); + job->scene = CTX_data_scene(C); + job->view_layer = CTX_data_view_layer(C); + job->wm = CTX_wm_manager(C); + job->import_ok = false; + BLI_strncpy(job->filename, filepath, 1024); + + job->settings.scale = scale; + job->settings.is_sequence = is_sequence; + job->settings.set_frame_range = set_frame_range; + job->settings.sequence_len = sequence_len; + job->settings.sequence_offset = offset; + job->settings.validate_meshes = validate_meshes; + job->error_code = ABC_NO_ERROR; + job->was_cancelled = false; + job->archive = NULL; + + G.is_break = false; + + bool import_ok = false; + if (as_background_job) { + wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C), + CTX_wm_window(C), + job->scene, + "Alembic Import", + WM_JOB_PROGRESS, + WM_JOB_TYPE_ALEMBIC); + + /* setup job */ + WM_jobs_customdata_set(wm_job, job, import_freejob); + WM_jobs_timer(wm_job, 0.1, NC_SCENE | ND_FRAME, NC_SCENE | ND_FRAME); + WM_jobs_callbacks(wm_job, import_startjob, NULL, NULL, import_endjob); + + WM_jobs_start(CTX_wm_manager(C), wm_job); + } + else { + /* Fake a job context, so that we don't need NULL pointer checks while importing. */ + short stop = 0, do_update = 0; + float progress = 0.f; + + import_startjob(job, &stop, &do_update, &progress); + import_endjob(job); + import_ok = job->import_ok; + + import_freejob(job); + } + + return import_ok; +} + +/* ************************************************************************** */ + +void ABC_get_transform(CacheReader *reader, float r_mat[4][4], float time, float scale) +{ + if (!reader) { + return; + } + + AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader); + + bool is_constant = false; + abc_reader->read_matrix(r_mat, time, scale, is_constant); +} + +/* ************************************************************************** */ + +static AbcObjectReader *get_abc_reader(CacheReader *reader, Object *ob, const char **err_str) +{ + AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader); + IObject iobject = abc_reader->iobject(); + + if (!iobject.valid()) { + *err_str = "Invalid object: verify object path"; + return NULL; + } + + const ObjectHeader &header = iobject.getHeader(); + if (!abc_reader->accepts_object_type(header, ob, err_str)) { + /* err_str is set by acceptsObjectType() */ + return NULL; + } + + return abc_reader; +} + +static ISampleSelector sample_selector_for_time(float time) +{ + /* kFloorIndex is used to be compatible with non-interpolating + * properties; they use the floor. */ + return ISampleSelector(time, ISampleSelector::kFloorIndex); +} + +Mesh *ABC_read_mesh(CacheReader *reader, + Object *ob, + Mesh *existing_mesh, + const float time, + const char **err_str, + int read_flag) +{ + AbcObjectReader *abc_reader = get_abc_reader(reader, ob, err_str); + if (abc_reader == NULL) { + return NULL; + } + + ISampleSelector sample_sel = sample_selector_for_time(time); + return abc_reader->read_mesh(existing_mesh, sample_sel, read_flag, err_str); +} + +bool ABC_mesh_topology_changed( + CacheReader *reader, Object *ob, Mesh *existing_mesh, const float time, const char **err_str) +{ + AbcObjectReader *abc_reader = get_abc_reader(reader, ob, err_str); + if (abc_reader == NULL) { + return false; + } + + ISampleSelector sample_sel = sample_selector_for_time(time); + return abc_reader->topology_changed(existing_mesh, sample_sel); +} + +/* ************************************************************************** */ + +void CacheReader_free(CacheReader *reader) +{ + AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader); + abc_reader->decref(); + + if (abc_reader->refcount() == 0) { + delete abc_reader; + } +} + +void CacheReader_incref(CacheReader *reader) +{ + AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader); + abc_reader->incref(); +} + +CacheReader *CacheReader_open_alembic_object(AbcArchiveHandle *handle, + CacheReader *reader, + Object *object, + const char *object_path) +{ + if (object_path[0] == '\0') { + return reader; + } + + ArchiveReader *archive = archive_from_handle(handle); + + if (!archive || !archive->valid()) { + return reader; + } + + IObject iobject; + find_iobject(archive->getTop(), iobject, object_path); + + if (reader) { + CacheReader_free(reader); + } + + ImportSettings settings; + AbcObjectReader *abc_reader = create_reader(iobject, settings); + if (abc_reader == NULL) { + /* This object is not supported */ + return NULL; + } + abc_reader->object(object); + abc_reader->incref(); + + return reinterpret_cast<CacheReader *>(abc_reader); +} diff --git a/source/blender/io/avi/AVI_avi.h b/source/blender/io/avi/AVI_avi.h new file mode 100644 index 00000000000..4f3aa720da3 --- /dev/null +++ b/source/blender/io/avi/AVI_avi.h @@ -0,0 +1,299 @@ +/* + * 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) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ + +/** \file + * \ingroup avi + * + * \section avi_about About the AVI module + * + * This is external code. It provides avi file import/export and + * conversions. It has been adapted to make use of Blender memory + * management functions, and because of this it needs module + * blenlib. You need to provide this lib when linking with libavi.a . + * + * \subsection avi_issues Known issues with AVI + * + * - avi uses #MEM_mallocN, #MEM_freeN from blenlib. + * - Not all functions that are used externally are properly + * prototyped. + * + * This header has not been split, since it interleaves type defines + * and functions. You would need the types to be able to include the + * function headers anyway. And, after all, it is someone else's + * code. So we keep it like this. + */ + +#ifndef __AVI_AVI_H__ +#define __AVI_AVI_H__ + +#include "BLI_sys_types.h" +#include <stdio.h> /* for FILE */ + +typedef struct _AviChunk { + int fcc; + int size; +} AviChunk; + +typedef struct _AviList { + int fcc; + int size; + int ids; +} AviList; + +typedef struct _AviMainHeader { + int fcc; + int size; + int MicroSecPerFrame; /* MicroSecPerFrame - timing between frames */ + int MaxBytesPerSec; /* MaxBytesPerSec - approx bps system must handle */ + int PaddingGranularity; + int Flags; + + /** had idx1 chunk */ +#define AVIF_HASINDEX 0x00000010 + /** must use idx1 chunk to determine order */ +#define AVIF_MUSTUSEINDEX 0x00000020 + /** AVI file is interleaved */ +#define AVIF_ISINTERLEAVED 0x00000100 +#define AVIF_TRUSTCKTYPE 0x00000800 + /** specially allocated used for capturing real time video */ +#define AVIF_WASCAPTUREFILE 0x00010000 + /** contains copyrighted data */ +#define AVIF_COPYRIGHTED 0x00020000 + + int TotalFrames; + int InitialFrames; /* InitialFrames - initial frame before interleaving */ + int Streams; + int SuggestedBufferSize; + int Width; + int Height; + int Reserved[4]; +} AviMainHeader; + +typedef struct _AviStreamHeader { + int fcc; + int size; + int Type; +#define AVIST_VIDEO FCC("vids") +#define AVIST_AUDIO FCC("auds") +#define AVIST_MIDI FCC("mids") +#define AVIST_TEXT FCC("txts") + + int Handler; + int Flags; +#define AVISF_DISABLED 0x00000001 +#define AVISF_VIDEO_PALCHANGES 0x00010000 + + short Priority; + short Language; + int InitialFrames; + int Scale; + int Rate; + int Start; + int Length; + int SuggestedBufferSize; + int Quality; + int SampleSize; + short left; + short top; + short right; + short bottom; +} AviStreamHeader; + +typedef struct _AviBitmapInfoHeader { + int fcc; + int size; + int Size; + int Width; + int Height; + short Planes; + short BitCount; + int Compression; + int SizeImage; + int XPelsPerMeter; + int YPelsPerMeter; + int ClrUsed; + int ClrImportant; +} AviBitmapInfoHeader; + +typedef struct _AviMJPEGUnknown { + int a; + int b; + int c; + int d; + int e; + int f; + int g; +} AviMJPEGUnknown; + +typedef struct _AviIndexEntry { + int ChunkId; + int Flags; +#define AVIIF_LIST 0x00000001 +#define AVIIF_KEYFRAME 0x00000010 +#define AVIIF_NO_TIME 0x00000100 +#define AVIIF_COMPRESSOR 0x0FFF0000 + int Offset; + int Size; +} AviIndexEntry; + +typedef struct _AviIndex { + int fcc; + int size; + AviIndexEntry *entrys; +} AviIndex; + +typedef enum { + /** The most basic of forms, 3 bytes per pixel, 1 per r, g, b. */ + AVI_FORMAT_RGB24, + /** The second most basic of forms, 4 bytes per pixel, 1 per r, g, b, alpha. */ + AVI_FORMAT_RGB32, + /** Same as above, but is in the weird AVI order (bottom to top, left to right). */ + AVI_FORMAT_AVI_RGB, + /** Motion-JPEG. */ + AVI_FORMAT_MJPEG, +} AviFormat; + +typedef struct _AviStreamRec { + AviStreamHeader sh; + void *sf; + int sf_size; + AviFormat format; +} AviStreamRec; + +typedef struct _AviMovie { + FILE *fp; + + int type; +#define AVI_MOVIE_READ 0 +#define AVI_MOVIE_WRITE 1 + + int64_t size; + + AviMainHeader *header; + AviStreamRec *streams; + AviIndexEntry *entries; + int index_entries; + + int64_t movi_offset; + int64_t read_offset; + int64_t *offset_table; + + /* Local data goes here */ + int interlace; + int odd_fields; +} AviMovie; + +typedef enum { + AVI_ERROR_NONE = 0, + AVI_ERROR_COMPRESSION, + AVI_ERROR_OPEN, + AVI_ERROR_READING, + AVI_ERROR_WRITING, + AVI_ERROR_FORMAT, + AVI_ERROR_ALLOC, + AVI_ERROR_FOUND, + AVI_ERROR_OPTION, +} AviError; + +/* belongs to the option-setting function. */ +typedef enum { + AVI_OPTION_WIDTH = 0, + AVI_OPTION_HEIGHT, + AVI_OPTION_QUALITY, + AVI_OPTION_FRAMERATE, +} AviOption; + +/* The offsets that will always stay the same in AVI files we + * write... used to seek around to the places where we need to write + * the sizes */ + +#define AVI_RIFF_SOFF 4L +#define AVI_HDRL_SOFF 16L + +/** + * This is a sort of MAKE_ID thing. Used in imbuf :( It is used + * through options in the AVI header (AviStreamHeader). */ +#define FCC(ch4) (ch4[0] | ch4[1] << 8 | ch4[2] << 16 | ch4[3] << 24) + +/** + * Test whether this is an avi-format. + */ +bool AVI_is_avi(const char *name); + +/** + * Open a compressed file, decompress it into memory. + */ +AviError AVI_open_compress(char *name, AviMovie *movie, int streams, ...); + +/** + * Finalize a compressed output stream. + */ +AviError AVI_close_compress(AviMovie *movie); + +/** + * Choose a compression option for \<movie\>. Possible options are + * AVI_OPTION_TYPE_MAIN, AVI_OPTION_TYPE_STRH, AVI_OPTION_TYPE_STRF + */ +AviError AVI_set_compress_option( + AviMovie *movie, int option_type, int stream, AviOption option, void *opt_data); +/* Hmmm... there should be some explanation about what these mean */ +/** + * Compression option, for use in avi_set_compress_option + */ +#define AVI_OPTION_TYPE_MAIN 0 +/** + * Compression option, for use in avi_set_compress_option + */ +#define AVI_OPTION_TYPE_STRH 1 +/** + * Compression option, for use in avi_set_compress_option + */ +#define AVI_OPTION_TYPE_STRF 2 + +/** + * Direct the streams \<avist_type\> to \<movie\>. Redirect \<stream_num\> + * streams. + */ +int AVI_get_stream(AviMovie *movie, int avist_type, int stream_num); + +/** + * Open a movie stream from file. + */ +AviError AVI_open_movie(const char *name, AviMovie *movie); + +/** + * Read a frame from a movie stream. + */ +void *AVI_read_frame(AviMovie *movie, AviFormat format, int frame, int stream); +/** + * Close an open movie stream. + */ +AviError AVI_close(AviMovie *movie); + +/** + * Write frames to a movie stream. + */ +AviError AVI_write_frame(AviMovie *movie, int frame_num, ...); + +/** + * Unused but still external + */ +AviError AVI_print_error(AviError error); + +#endif /* __AVI_AVI_H__ */ diff --git a/source/blender/io/avi/CMakeLists.txt b/source/blender/io/avi/CMakeLists.txt new file mode 100644 index 00000000000..76c90353673 --- /dev/null +++ b/source/blender/io/avi/CMakeLists.txt @@ -0,0 +1,53 @@ +# ***** 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) 2006, Blender Foundation +# All rights reserved. +# ***** END GPL LICENSE BLOCK ***** + +set(INC + . + ../../blenlib + ../../imbuf + ../../../../intern/guardedalloc +) + +set(INC_SYS + ${JPEG_INCLUDE_DIR} +) + +set(SRC + intern/avi.c + intern/avi_codecs.c + intern/avi_endian.c + intern/avi_mjpeg.c + intern/avi_options.c + intern/avi_rgb.c + intern/avi_rgb32.c + + AVI_avi.h + intern/avi_endian.h + intern/avi_intern.h + intern/avi_mjpeg.h + intern/avi_rgb.h + intern/avi_rgb32.h +) + +set(LIB + ${JPEG_LIBRARIES} +) + +blender_add_lib(bf_avi "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/io/avi/intern/avi.c b/source/blender/io/avi/intern/avi.c new file mode 100644 index 00000000000..22eb0be0cc0 --- /dev/null +++ b/source/blender/io/avi/intern/avi.c @@ -0,0 +1,1056 @@ +/* + * 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) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ + +/** \file + * \ingroup avi + * + * This is external code. + */ + +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <stdio.h> +#include <ctype.h> + +#ifdef WIN32 +# include "BLI_winstuff.h" +#endif + +#include "MEM_guardedalloc.h" + +#include "BLI_sys_types.h" +#include "BLI_utildefines.h" +#include "BLI_fileops.h" + +#include "AVI_avi.h" +#include "avi_intern.h" + +#include "avi_endian.h" + +static int AVI_DEBUG = 0; +static char DEBUG_FCC[4]; + +#define DEBUG_PRINT(x) \ + if (AVI_DEBUG) { \ + printf("AVI DEBUG: " x); \ + } \ + (void)0 + +/* local functions */ +char *fcc_to_char(unsigned int fcc); +char *tcc_to_char(unsigned int tcc); + +/* implementation */ + +unsigned int GET_FCC(FILE *fp) +{ + unsigned char tmp[4]; + + tmp[0] = getc(fp); + tmp[1] = getc(fp); + tmp[2] = getc(fp); + tmp[3] = getc(fp); + + return FCC(tmp); +} + +unsigned int GET_TCC(FILE *fp) +{ + char tmp[5]; + + tmp[0] = getc(fp); + tmp[1] = getc(fp); + tmp[2] = 0; + tmp[3] = 0; + + return FCC(tmp); +} + +char *fcc_to_char(unsigned int fcc) +{ + DEBUG_FCC[0] = (fcc)&127; + DEBUG_FCC[1] = (fcc >> 8) & 127; + DEBUG_FCC[2] = (fcc >> 16) & 127; + DEBUG_FCC[3] = (fcc >> 24) & 127; + + return DEBUG_FCC; +} + +char *tcc_to_char(unsigned int tcc) +{ + DEBUG_FCC[0] = (tcc)&127; + DEBUG_FCC[1] = (tcc >> 8) & 127; + DEBUG_FCC[2] = 0; + DEBUG_FCC[3] = 0; + + return DEBUG_FCC; +} + +int AVI_get_stream(AviMovie *movie, int avist_type, int stream_num) +{ + int cur_stream; + + if (movie == NULL) { + return -AVI_ERROR_OPTION; + } + + for (cur_stream = 0; cur_stream < movie->header->Streams; cur_stream++) { + if (movie->streams[cur_stream].sh.Type == avist_type) { + if (stream_num == 0) { + return cur_stream; + } + else { + stream_num--; + } + } + } + + return -AVI_ERROR_FOUND; +} + +static int fcc_get_stream(int fcc) +{ + char fccs[4]; + + fccs[0] = fcc; + fccs[1] = fcc >> 8; + fccs[2] = fcc >> 16; + fccs[3] = fcc >> 24; + + return 10 * (fccs[0] - '0') + (fccs[1] - '0'); +} + +static bool fcc_is_data(int fcc) +{ + char fccs[4]; + + fccs[0] = fcc; + fccs[1] = fcc >> 8; + fccs[2] = fcc >> 16; + fccs[3] = fcc >> 24; + + if (!isdigit(fccs[0]) || !isdigit(fccs[1]) || (fccs[2] != 'd' && fccs[2] != 'w')) { + return 0; + } + if (fccs[3] != 'b' && fccs[3] != 'c') { + return 0; + } + + return 1; +} + +AviError AVI_print_error(AviError in_error) +{ + int error; + + if ((int)in_error < 0) { + error = -in_error; + } + else { + error = in_error; + } + + switch (error) { + case AVI_ERROR_NONE: + break; + case AVI_ERROR_COMPRESSION: + printf("AVI ERROR: compressed in an unsupported format\n"); + break; + case AVI_ERROR_OPEN: + printf("AVI ERROR: could not open file\n"); + break; + case AVI_ERROR_READING: + printf("AVI ERROR: could not read from file\n"); + break; + case AVI_ERROR_WRITING: + printf("AVI ERROR: could not write to file\n"); + break; + case AVI_ERROR_FORMAT: + printf("AVI ERROR: file is in an illegal or unrecognized format\n"); + break; + case AVI_ERROR_ALLOC: + printf("AVI ERROR: error encountered while allocating memory\n"); + break; + case AVI_ERROR_OPTION: + printf("AVI ERROR: program made illegal request\n"); + break; + case AVI_ERROR_FOUND: + printf("AVI ERROR: movie did not contain expected item\n"); + break; + default: + break; + } + + return in_error; +} + +bool AVI_is_avi(const char *name) +{ + int temp, fcca, j; + AviMovie movie = {NULL}; + AviMainHeader header; + AviBitmapInfoHeader bheader; + int movie_tracks = 0; + + DEBUG_PRINT("opening movie\n"); + + movie.type = AVI_MOVIE_READ; + movie.fp = BLI_fopen(name, "rb"); + movie.offset_table = NULL; + + if (movie.fp == NULL) { + return 0; + } + + if (GET_FCC(movie.fp) != FCC("RIFF") || !(movie.size = GET_FCC(movie.fp))) { + fclose(movie.fp); + return 0; + } + + movie.header = &header; + + if (GET_FCC(movie.fp) != FCC("AVI ") || GET_FCC(movie.fp) != FCC("LIST") || !GET_FCC(movie.fp) || + GET_FCC(movie.fp) != FCC("hdrl") || (movie.header->fcc = GET_FCC(movie.fp)) != FCC("avih") || + !(movie.header->size = GET_FCC(movie.fp))) { + DEBUG_PRINT("bad initial header info\n"); + fclose(movie.fp); + return 0; + } + + movie.header->MicroSecPerFrame = GET_FCC(movie.fp); + movie.header->MaxBytesPerSec = GET_FCC(movie.fp); + movie.header->PaddingGranularity = GET_FCC(movie.fp); + movie.header->Flags = GET_FCC(movie.fp); + movie.header->TotalFrames = GET_FCC(movie.fp); + movie.header->InitialFrames = GET_FCC(movie.fp); + movie.header->Streams = GET_FCC(movie.fp); + movie.header->SuggestedBufferSize = GET_FCC(movie.fp); + movie.header->Width = GET_FCC(movie.fp); + movie.header->Height = GET_FCC(movie.fp); + movie.header->Reserved[0] = GET_FCC(movie.fp); + movie.header->Reserved[1] = GET_FCC(movie.fp); + movie.header->Reserved[2] = GET_FCC(movie.fp); + movie.header->Reserved[3] = GET_FCC(movie.fp); + + fseek(movie.fp, movie.header->size - 14 * 4, SEEK_CUR); + + /* Limit number of streams to some reasonable amount to prevent + * buffer overflow vulnerabilities. */ + if (movie.header->Streams < 1 || movie.header->Streams > 65536) { + DEBUG_PRINT("Number of streams should be in range 1-65536\n"); + fclose(movie.fp); + return 0; + } + + movie.streams = (AviStreamRec *)MEM_calloc_arrayN( + movie.header->Streams, sizeof(AviStreamRec), "moviestreams"); + + for (temp = 0; temp < movie.header->Streams; temp++) { + + if (GET_FCC(movie.fp) != FCC("LIST") || !GET_FCC(movie.fp) || + GET_FCC(movie.fp) != FCC("strl") || + (movie.streams[temp].sh.fcc = GET_FCC(movie.fp)) != FCC("strh") || + !(movie.streams[temp].sh.size = GET_FCC(movie.fp))) { + DEBUG_PRINT("bad stream header information\n"); + + MEM_freeN(movie.streams); + fclose(movie.fp); + return 0; + } + + movie.streams[temp].sh.Type = GET_FCC(movie.fp); + movie.streams[temp].sh.Handler = GET_FCC(movie.fp); + + fcca = movie.streams[temp].sh.Handler; + + if (movie.streams[temp].sh.Type == FCC("vids")) { + if (fcca == FCC("DIB ") || fcca == FCC("RGB ") || fcca == FCC("rgb ") || + fcca == FCC("RAW ") || fcca == 0) { + movie.streams[temp].format = AVI_FORMAT_AVI_RGB; + } + else if (fcca == FCC("mjpg") || fcca == FCC("MJPG")) { + movie.streams[temp].format = AVI_FORMAT_MJPEG; + } + else { + MEM_freeN(movie.streams); + fclose(movie.fp); + return 0; + } + movie_tracks++; + } + + movie.streams[temp].sh.Flags = GET_FCC(movie.fp); + movie.streams[temp].sh.Priority = GET_TCC(movie.fp); + movie.streams[temp].sh.Language = GET_TCC(movie.fp); + movie.streams[temp].sh.InitialFrames = GET_FCC(movie.fp); + movie.streams[temp].sh.Scale = GET_FCC(movie.fp); + movie.streams[temp].sh.Rate = GET_FCC(movie.fp); + movie.streams[temp].sh.Start = GET_FCC(movie.fp); + movie.streams[temp].sh.Length = GET_FCC(movie.fp); + movie.streams[temp].sh.SuggestedBufferSize = GET_FCC(movie.fp); + movie.streams[temp].sh.Quality = GET_FCC(movie.fp); + movie.streams[temp].sh.SampleSize = GET_FCC(movie.fp); + movie.streams[temp].sh.left = GET_TCC(movie.fp); + movie.streams[temp].sh.top = GET_TCC(movie.fp); + movie.streams[temp].sh.right = GET_TCC(movie.fp); + movie.streams[temp].sh.bottom = GET_TCC(movie.fp); + + fseek(movie.fp, movie.streams[temp].sh.size - 14 * 4, SEEK_CUR); + + if (GET_FCC(movie.fp) != FCC("strf")) { + DEBUG_PRINT("no stream format information\n"); + MEM_freeN(movie.streams); + fclose(movie.fp); + return 0; + } + + movie.streams[temp].sf_size = GET_FCC(movie.fp); + if (movie.streams[temp].sh.Type == FCC("vids")) { + j = movie.streams[temp].sf_size - (sizeof(AviBitmapInfoHeader) - 8); + if (j >= 0) { + AviBitmapInfoHeader *bi; + + movie.streams[temp].sf = &bheader; + bi = (AviBitmapInfoHeader *)movie.streams[temp].sf; + + bi->fcc = FCC("strf"); + bi->size = movie.streams[temp].sf_size; + bi->Size = GET_FCC(movie.fp); + bi->Width = GET_FCC(movie.fp); + bi->Height = GET_FCC(movie.fp); + bi->Planes = GET_TCC(movie.fp); + bi->BitCount = GET_TCC(movie.fp); + bi->Compression = GET_FCC(movie.fp); + bi->SizeImage = GET_FCC(movie.fp); + bi->XPelsPerMeter = GET_FCC(movie.fp); + bi->YPelsPerMeter = GET_FCC(movie.fp); + bi->ClrUsed = GET_FCC(movie.fp); + bi->ClrImportant = GET_FCC(movie.fp); + + fcca = bi->Compression; + + if (movie.streams[temp].format == AVI_FORMAT_AVI_RGB) { + if (fcca == FCC("DIB ") || fcca == FCC("RGB ") || fcca == FCC("rgb ") || + fcca == FCC("RAW ") || fcca == 0) { + /* pass */ + } + else if (fcca == FCC("mjpg") || fcca == FCC("MJPG")) { + movie.streams[temp].format = AVI_FORMAT_MJPEG; + } + else { + MEM_freeN(movie.streams); + fclose(movie.fp); + return 0; + } + } + } + if (j > 0) { + fseek(movie.fp, j, SEEK_CUR); + } + } + else { + fseek(movie.fp, movie.streams[temp].sf_size, SEEK_CUR); + } + + /* Walk to the next LIST */ + while (GET_FCC(movie.fp) != FCC("LIST")) { + temp = GET_FCC(movie.fp); + if (temp < 0 || ftell(movie.fp) > movie.size) { + DEBUG_PRINT("incorrect size in header or error in AVI\n"); + + MEM_freeN(movie.streams); + fclose(movie.fp); + return 0; + } + fseek(movie.fp, temp, SEEK_CUR); + } + + fseek(movie.fp, -4L, SEEK_CUR); + } + + MEM_freeN(movie.streams); + fclose(movie.fp); + + /* at least one video track is needed */ + return (movie_tracks != 0); +} + +AviError AVI_open_movie(const char *name, AviMovie *movie) +{ + int temp, fcca, size, j; + + DEBUG_PRINT("opening movie\n"); + + memset(movie, 0, sizeof(AviMovie)); + + movie->type = AVI_MOVIE_READ; + movie->fp = BLI_fopen(name, "rb"); + movie->offset_table = NULL; + + if (movie->fp == NULL) { + return AVI_ERROR_OPEN; + } + + if (GET_FCC(movie->fp) != FCC("RIFF") || !(movie->size = GET_FCC(movie->fp))) { + return AVI_ERROR_FORMAT; + } + + movie->header = (AviMainHeader *)MEM_mallocN(sizeof(AviMainHeader), "movieheader"); + + if (GET_FCC(movie->fp) != FCC("AVI ") || GET_FCC(movie->fp) != FCC("LIST") || + !GET_FCC(movie->fp) || GET_FCC(movie->fp) != FCC("hdrl") || + (movie->header->fcc = GET_FCC(movie->fp)) != FCC("avih") || + !(movie->header->size = GET_FCC(movie->fp))) { + DEBUG_PRINT("bad initial header info\n"); + return AVI_ERROR_FORMAT; + } + + movie->header->MicroSecPerFrame = GET_FCC(movie->fp); + movie->header->MaxBytesPerSec = GET_FCC(movie->fp); + movie->header->PaddingGranularity = GET_FCC(movie->fp); + movie->header->Flags = GET_FCC(movie->fp); + movie->header->TotalFrames = GET_FCC(movie->fp); + movie->header->InitialFrames = GET_FCC(movie->fp); + movie->header->Streams = GET_FCC(movie->fp); + movie->header->SuggestedBufferSize = GET_FCC(movie->fp); + movie->header->Width = GET_FCC(movie->fp); + movie->header->Height = GET_FCC(movie->fp); + movie->header->Reserved[0] = GET_FCC(movie->fp); + movie->header->Reserved[1] = GET_FCC(movie->fp); + movie->header->Reserved[2] = GET_FCC(movie->fp); + movie->header->Reserved[3] = GET_FCC(movie->fp); + + fseek(movie->fp, movie->header->size - 14 * 4, SEEK_CUR); + + /* Limit number of streams to some reasonable amount to prevent + * buffer overflow vulnerabilities. */ + if (movie->header->Streams < 1 || movie->header->Streams > 65536) { + DEBUG_PRINT("Number of streams should be in range 1-65536\n"); + return AVI_ERROR_FORMAT; + } + + movie->streams = (AviStreamRec *)MEM_calloc_arrayN( + movie->header->Streams, sizeof(AviStreamRec), "moviestreams"); + + for (temp = 0; temp < movie->header->Streams; temp++) { + + if (GET_FCC(movie->fp) != FCC("LIST") || !GET_FCC(movie->fp) || + GET_FCC(movie->fp) != FCC("strl") || + (movie->streams[temp].sh.fcc = GET_FCC(movie->fp)) != FCC("strh") || + !(movie->streams[temp].sh.size = GET_FCC(movie->fp))) { + DEBUG_PRINT("bad stream header information\n"); + return AVI_ERROR_FORMAT; + } + + movie->streams[temp].sh.Type = GET_FCC(movie->fp); + movie->streams[temp].sh.Handler = GET_FCC(movie->fp); + + fcca = movie->streams[temp].sh.Handler; + + if (movie->streams[temp].sh.Type == FCC("vids")) { + if (fcca == FCC("DIB ") || fcca == FCC("RGB ") || fcca == FCC("rgb ") || + fcca == FCC("RAW ") || fcca == 0) { + movie->streams[temp].format = AVI_FORMAT_AVI_RGB; + } + else if (fcca == FCC("mjpg") || fcca == FCC("MJPG")) { + movie->streams[temp].format = AVI_FORMAT_MJPEG; + } + else { + return AVI_ERROR_COMPRESSION; + } + } + + movie->streams[temp].sh.Flags = GET_FCC(movie->fp); + movie->streams[temp].sh.Priority = GET_TCC(movie->fp); + movie->streams[temp].sh.Language = GET_TCC(movie->fp); + movie->streams[temp].sh.InitialFrames = GET_FCC(movie->fp); + movie->streams[temp].sh.Scale = GET_FCC(movie->fp); + movie->streams[temp].sh.Rate = GET_FCC(movie->fp); + movie->streams[temp].sh.Start = GET_FCC(movie->fp); + movie->streams[temp].sh.Length = GET_FCC(movie->fp); + movie->streams[temp].sh.SuggestedBufferSize = GET_FCC(movie->fp); + movie->streams[temp].sh.Quality = GET_FCC(movie->fp); + movie->streams[temp].sh.SampleSize = GET_FCC(movie->fp); + movie->streams[temp].sh.left = GET_TCC(movie->fp); + movie->streams[temp].sh.top = GET_TCC(movie->fp); + movie->streams[temp].sh.right = GET_TCC(movie->fp); + movie->streams[temp].sh.bottom = GET_TCC(movie->fp); + + fseek(movie->fp, movie->streams[temp].sh.size - 14 * 4, SEEK_CUR); + + if (GET_FCC(movie->fp) != FCC("strf")) { + DEBUG_PRINT("no stream format information\n"); + return AVI_ERROR_FORMAT; + } + + movie->streams[temp].sf_size = GET_FCC(movie->fp); + if (movie->streams[temp].sh.Type == FCC("vids")) { + j = movie->streams[temp].sf_size - (sizeof(AviBitmapInfoHeader) - 8); + if (j >= 0) { + AviBitmapInfoHeader *bi; + + movie->streams[temp].sf = MEM_mallocN(sizeof(AviBitmapInfoHeader), "streamformat"); + + bi = (AviBitmapInfoHeader *)movie->streams[temp].sf; + + bi->fcc = FCC("strf"); + bi->size = movie->streams[temp].sf_size; + bi->Size = GET_FCC(movie->fp); + bi->Width = GET_FCC(movie->fp); + bi->Height = GET_FCC(movie->fp); + bi->Planes = GET_TCC(movie->fp); + bi->BitCount = GET_TCC(movie->fp); + bi->Compression = GET_FCC(movie->fp); + bi->SizeImage = GET_FCC(movie->fp); + bi->XPelsPerMeter = GET_FCC(movie->fp); + bi->YPelsPerMeter = GET_FCC(movie->fp); + bi->ClrUsed = GET_FCC(movie->fp); + bi->ClrImportant = GET_FCC(movie->fp); + + fcca = bi->Compression; + + if (movie->streams[temp].format == AVI_FORMAT_AVI_RGB) { + if (fcca == FCC("DIB ") || fcca == FCC("RGB ") || fcca == FCC("rgb ") || + fcca == FCC("RAW ") || fcca == 0) { + /* pass */ + } + else if (fcca == FCC("mjpg") || fcca == FCC("MJPG")) { + movie->streams[temp].format = AVI_FORMAT_MJPEG; + } + else { + return AVI_ERROR_COMPRESSION; + } + } + } + if (j > 0) { + fseek(movie->fp, j, SEEK_CUR); + } + } + else { + fseek(movie->fp, movie->streams[temp].sf_size, SEEK_CUR); + } + + /* Walk to the next LIST */ + while (GET_FCC(movie->fp) != FCC("LIST")) { + temp = GET_FCC(movie->fp); + if (temp < 0 || ftell(movie->fp) > movie->size) { + DEBUG_PRINT("incorrect size in header or error in AVI\n"); + return AVI_ERROR_FORMAT; + } + fseek(movie->fp, temp, SEEK_CUR); + } + + fseek(movie->fp, -4L, SEEK_CUR); + } + + while (1) { + temp = GET_FCC(movie->fp); + size = GET_FCC(movie->fp); + + if (size == 0) { + break; + } + + if (temp == FCC("LIST")) { + if (GET_FCC(movie->fp) == FCC("movi")) { + break; + } + else { + fseek(movie->fp, size - 4, SEEK_CUR); + } + } + else { + fseek(movie->fp, size, SEEK_CUR); + } + if (ftell(movie->fp) > movie->size) { + DEBUG_PRINT("incorrect size in header or error in AVI\n"); + return AVI_ERROR_FORMAT; + } + } + + movie->movi_offset = ftell(movie->fp); + movie->read_offset = movie->movi_offset; + + /* Read in the index if the file has one, otherwise create one */ + if (movie->header->Flags & AVIF_HASINDEX) { + fseek(movie->fp, size - 4, SEEK_CUR); + + if (GET_FCC(movie->fp) != FCC("idx1")) { + DEBUG_PRINT("bad index informatio\n"); + return AVI_ERROR_FORMAT; + } + + movie->index_entries = GET_FCC(movie->fp) / sizeof(AviIndexEntry); + if (movie->index_entries == 0) { + DEBUG_PRINT("no index entries\n"); + return AVI_ERROR_FORMAT; + } + + movie->entries = (AviIndexEntry *)MEM_mallocN(movie->index_entries * sizeof(AviIndexEntry), + "movieentries"); + + for (temp = 0; temp < movie->index_entries; temp++) { + movie->entries[temp].ChunkId = GET_FCC(movie->fp); + movie->entries[temp].Flags = GET_FCC(movie->fp); + movie->entries[temp].Offset = GET_FCC(movie->fp); + movie->entries[temp].Size = GET_FCC(movie->fp); + + if (AVI_DEBUG) { + printf("Index entry %04d: ChunkId:%s Flags:%d Offset:%d Size:%d\n", + temp, + fcc_to_char(movie->entries[temp].ChunkId), + movie->entries[temp].Flags, + movie->entries[temp].Offset, + movie->entries[temp].Size); + } + } + + /* Some AVI's have offset entries in absolute coordinates + * instead of an offset from the movie beginning... this is... + * wacky, but we need to handle it. The wacky offset always + * starts at movi_offset it seems... so we'll check that. + * Note the offset needs an extra 4 bytes for some + * undetermined reason */ + + if (movie->entries[0].Offset == movie->movi_offset) { + movie->read_offset = 4; + } + } + + DEBUG_PRINT("movie successfully opened\n"); + return AVI_ERROR_NONE; +} + +void *AVI_read_frame(AviMovie *movie, AviFormat format, int frame, int stream) +{ + int cur_frame = -1, i = 0, rewind = 1; + void *buffer; + + /* Retrieve the record number of the desired frame in the index + * If a chunk has Size 0 we need to rewind to previous frame */ + while (rewind && frame > -1) { + i = 0; + cur_frame = -1; + rewind = 0; + + while (cur_frame < frame && i < movie->index_entries) { + if (fcc_is_data(movie->entries[i].ChunkId) && + fcc_get_stream(movie->entries[i].ChunkId) == stream) { + if ((cur_frame == frame - 1) && (movie->entries[i].Size == 0)) { + rewind = 1; + frame = frame - 1; + } + else { + cur_frame++; + } + } + i++; + } + } + + if (cur_frame != frame) { + return NULL; + } + + fseek(movie->fp, movie->read_offset + movie->entries[i - 1].Offset, SEEK_SET); + + size_t size = GET_FCC(movie->fp); + buffer = MEM_mallocN(size, "readbuffer"); + + if (fread(buffer, 1, size, movie->fp) != size) { + MEM_freeN(buffer); + + return NULL; + } + + buffer = avi_format_convert(movie, stream, buffer, movie->streams[stream].format, format, &size); + + return buffer; +} + +AviError AVI_close(AviMovie *movie) +{ + int i; + + fclose(movie->fp); + + for (i = 0; i < movie->header->Streams; i++) { + if (movie->streams[i].sf != NULL) { + MEM_freeN(movie->streams[i].sf); + } + } + + MEM_freeN(movie->header); + MEM_freeN(movie->streams); + + if (movie->entries != NULL) { + MEM_freeN(movie->entries); + } + if (movie->offset_table != NULL) { + MEM_freeN(movie->offset_table); + } + + return AVI_ERROR_NONE; +} + +AviError AVI_open_compress(char *name, AviMovie *movie, int streams, ...) +{ + va_list ap; + AviList list; + AviChunk chunk; + int i; + int64_t header_pos1, header_pos2; + int64_t stream_pos1, stream_pos2; + int64_t junk_pos; + + movie->type = AVI_MOVIE_WRITE; + movie->fp = BLI_fopen(name, "wb"); + + movie->index_entries = 0; + + if (movie->fp == NULL) { + return AVI_ERROR_OPEN; + } + + movie->offset_table = (int64_t *)MEM_mallocN((1 + streams * 2) * sizeof(int64_t), "offsettable"); + + for (i = 0; i < 1 + streams * 2; i++) { + movie->offset_table[i] = -1L; + } + + movie->entries = NULL; + + movie->header = (AviMainHeader *)MEM_mallocN(sizeof(AviMainHeader), "movieheader"); + + movie->header->fcc = FCC("avih"); + movie->header->size = 56; + movie->header->MicroSecPerFrame = 66667; + movie->header->MaxBytesPerSec = 0; + movie->header->PaddingGranularity = 0; + movie->header->Flags = AVIF_HASINDEX | AVIF_MUSTUSEINDEX; + movie->header->TotalFrames = 0; + movie->header->InitialFrames = 0; + movie->header->Streams = streams; + movie->header->SuggestedBufferSize = 0; + movie->header->Width = 0; + movie->header->Height = 0; + movie->header->Reserved[0] = 0; + movie->header->Reserved[1] = 0; + movie->header->Reserved[2] = 0; + movie->header->Reserved[3] = 0; + + /* Limit number of streams to some reasonable amount to prevent + * buffer overflow vulnerabilities. */ + if (movie->header->Streams < 0 || movie->header->Streams > 65536) { + DEBUG_PRINT("Number of streams should be in range 0-65536\n"); + return AVI_ERROR_FORMAT; + } + + movie->streams = (AviStreamRec *)MEM_mallocN(sizeof(AviStreamRec) * movie->header->Streams, + "moviestreams"); + + va_start(ap, streams); + + for (i = 0; i < movie->header->Streams; i++) { + movie->streams[i].format = va_arg(ap, AviFormat); + + movie->streams[i].sh.fcc = FCC("strh"); + movie->streams[i].sh.size = 56; + movie->streams[i].sh.Type = avi_get_format_type(movie->streams[i].format); + if (movie->streams[i].sh.Type == 0) { + va_end(ap); + return AVI_ERROR_FORMAT; + } + + movie->streams[i].sh.Handler = avi_get_format_fcc(movie->streams[i].format); + if (movie->streams[i].sh.Handler == 0) { + va_end(ap); + return AVI_ERROR_FORMAT; + } + + movie->streams[i].sh.Flags = 0; + movie->streams[i].sh.Priority = 0; + movie->streams[i].sh.Language = 0; + movie->streams[i].sh.InitialFrames = 0; + movie->streams[i].sh.Scale = 66667; + movie->streams[i].sh.Rate = 1000000; + movie->streams[i].sh.Start = 0; + movie->streams[i].sh.Length = 0; + movie->streams[i].sh.SuggestedBufferSize = 0; + movie->streams[i].sh.Quality = 10000; + movie->streams[i].sh.SampleSize = 0; + movie->streams[i].sh.left = 0; + movie->streams[i].sh.top = 0; + movie->streams[i].sh.right = 0; + movie->streams[i].sh.bottom = 0; + + if (movie->streams[i].sh.Type == FCC("vids")) { + movie->streams[i].sf = MEM_mallocN(sizeof(AviBitmapInfoHeader), "moviestreamformatS"); + movie->streams[i].sf_size = sizeof(AviBitmapInfoHeader); + + ((AviBitmapInfoHeader *)movie->streams[i].sf)->fcc = FCC("strf"); + ((AviBitmapInfoHeader *)movie->streams[i].sf)->size = movie->streams[i].sf_size - 8; + ((AviBitmapInfoHeader *)movie->streams[i].sf)->Size = movie->streams[i].sf_size - 8; + ((AviBitmapInfoHeader *)movie->streams[i].sf)->Width = 0; + ((AviBitmapInfoHeader *)movie->streams[i].sf)->Height = 0; + ((AviBitmapInfoHeader *)movie->streams[i].sf)->Planes = 1; + ((AviBitmapInfoHeader *)movie->streams[i].sf)->BitCount = 24; + ((AviBitmapInfoHeader *)movie->streams[i].sf)->Compression = avi_get_format_compression( + movie->streams[i].format); + ((AviBitmapInfoHeader *)movie->streams[i].sf)->SizeImage = 0; + ((AviBitmapInfoHeader *)movie->streams[i].sf)->XPelsPerMeter = 0; + ((AviBitmapInfoHeader *)movie->streams[i].sf)->YPelsPerMeter = 0; + ((AviBitmapInfoHeader *)movie->streams[i].sf)->ClrUsed = 0; + ((AviBitmapInfoHeader *)movie->streams[i].sf)->ClrImportant = 0; + } + } + + list.fcc = FCC("RIFF"); + list.size = 0; + list.ids = FCC("AVI "); + + awrite(movie, &list, 1, sizeof(AviList), movie->fp, AVI_LIST); + + list.fcc = FCC("LIST"); + list.size = 0; + list.ids = FCC("hdrl"); + + awrite(movie, &list, 1, sizeof(AviList), movie->fp, AVI_LIST); + + header_pos1 = ftell(movie->fp); + + movie->offset_table[0] = ftell(movie->fp); + + awrite(movie, movie->header, 1, sizeof(AviMainHeader), movie->fp, AVI_MAINH); + + for (i = 0; i < movie->header->Streams; i++) { + list.fcc = FCC("LIST"); + list.size = 0; + list.ids = FCC("strl"); + + awrite(movie, &list, 1, sizeof(AviList), movie->fp, AVI_LIST); + + stream_pos1 = ftell(movie->fp); + + movie->offset_table[1 + i * 2] = ftell(movie->fp); + awrite(movie, &movie->streams[i].sh, 1, sizeof(AviStreamHeader), movie->fp, AVI_STREAMH); + + movie->offset_table[1 + i * 2 + 1] = ftell(movie->fp); + awrite(movie, movie->streams[i].sf, 1, movie->streams[i].sf_size, movie->fp, AVI_BITMAPH); + + stream_pos2 = ftell(movie->fp); + + fseek(movie->fp, stream_pos1 - 8, SEEK_SET); + + PUT_FCCN((stream_pos2 - stream_pos1 + 4L), movie->fp); + + fseek(movie->fp, stream_pos2, SEEK_SET); + } + + junk_pos = ftell(movie->fp); + + if (junk_pos < 2024 - 8) { + chunk.fcc = FCC("JUNK"); + chunk.size = 2024 - 8 - (int)junk_pos; + + awrite(movie, &chunk, 1, sizeof(AviChunk), movie->fp, AVI_CHUNK); + + for (i = 0; i < chunk.size; i++) { + putc(0, movie->fp); + } + } + + header_pos2 = ftell(movie->fp); + + list.fcc = FCC("LIST"); + list.size = 0; + list.ids = FCC("movi"); + + awrite(movie, &list, 1, sizeof(AviList), movie->fp, AVI_LIST); + + movie->movi_offset = ftell(movie->fp) - 8L; + + fseek(movie->fp, AVI_HDRL_SOFF, SEEK_SET); + + PUT_FCCN((header_pos2 - header_pos1 + 4L), movie->fp); + + va_end(ap); + + return AVI_ERROR_NONE; +} + +AviError AVI_write_frame(AviMovie *movie, int frame_num, ...) +{ + AviList list; + AviChunk chunk; + va_list ap; + int stream; + int64_t rec_off; + AviFormat format; + void *buffer; + + if (frame_num < 0) { + return AVI_ERROR_OPTION; + } + + /* Allocate the new memory for the index entry */ + + if (frame_num >= movie->index_entries) { + const size_t entry_size = (movie->header->Streams + 1) * sizeof(AviIndexEntry); + movie->entries = (AviIndexEntry *)MEM_recallocN(movie->entries, (frame_num + 1) * entry_size); + movie->index_entries = frame_num + 1; + } + + /* Slap a new record entry onto the end of the file */ + + fseek(movie->fp, 0L, SEEK_END); + + list.fcc = FCC("LIST"); + list.size = 0; + list.ids = FCC("rec "); + + awrite(movie, &list, 1, sizeof(AviList), movie->fp, AVI_LIST); + + rec_off = ftell(movie->fp) - 8L; + + /* Write a frame for every stream */ + + va_start(ap, frame_num); + + for (stream = 0; stream < movie->header->Streams; stream++) { + unsigned int tbuf = 0; + + format = va_arg(ap, AviFormat); + buffer = va_arg(ap, void *); + size_t size = va_arg(ap, int); + + /* Convert the buffer into the output format */ + buffer = avi_format_convert( + movie, stream, buffer, format, movie->streams[stream].format, &size); + + /* Write the header info for this data chunk */ + + fseek(movie->fp, 0L, SEEK_END); + + chunk.fcc = avi_get_data_id(format, stream); + chunk.size = size; + + if (size % 4) { + chunk.size += 4 - size % 4; + } + + awrite(movie, &chunk, 1, sizeof(AviChunk), movie->fp, AVI_CHUNK); + + /* Write the index entry for this data chunk */ + + movie->entries[frame_num * (movie->header->Streams + 1) + stream + 1].ChunkId = chunk.fcc; + movie->entries[frame_num * (movie->header->Streams + 1) + stream + 1].Flags = AVIIF_KEYFRAME; + movie->entries[frame_num * (movie->header->Streams + 1) + stream + 1].Offset = + (int)(ftell(movie->fp) - 12L - movie->movi_offset); + movie->entries[frame_num * (movie->header->Streams + 1) + stream + 1].Size = chunk.size; + + /* Write the chunk */ + awrite(movie, buffer, 1, size, movie->fp, AVI_RAW); + MEM_freeN(buffer); + + if (size % 4) { + awrite(movie, &tbuf, 1, 4 - size % 4, movie->fp, AVI_RAW); + } + + /* Update the stream headers length field */ + movie->streams[stream].sh.Length++; + fseek(movie->fp, movie->offset_table[1 + stream * 2], SEEK_SET); + awrite(movie, &movie->streams[stream].sh, 1, sizeof(AviStreamHeader), movie->fp, AVI_STREAMH); + } + va_end(ap); + + /* Record the entry for the new record */ + + fseek(movie->fp, 0L, SEEK_END); + + movie->entries[frame_num * (movie->header->Streams + 1)].ChunkId = FCC("rec "); + movie->entries[frame_num * (movie->header->Streams + 1)].Flags = AVIIF_LIST; + movie->entries[frame_num * (movie->header->Streams + 1)].Offset = (int)(rec_off - 8L - + movie->movi_offset); + movie->entries[frame_num * (movie->header->Streams + 1)].Size = (int)(ftell(movie->fp) - + (rec_off + 4L)); + + /* Update the record size */ + fseek(movie->fp, rec_off, SEEK_SET); + PUT_FCCN(movie->entries[frame_num * (movie->header->Streams + 1)].Size, movie->fp); + + /* Update the main header information in the file */ + movie->header->TotalFrames++; + fseek(movie->fp, movie->offset_table[0], SEEK_SET); + awrite(movie, movie->header, 1, sizeof(AviMainHeader), movie->fp, AVI_MAINH); + + return AVI_ERROR_NONE; +} + +AviError AVI_close_compress(AviMovie *movie) +{ + int temp, movi_size, i; + + if (movie->fp == NULL) { + /* none of the allocations below were done if the file failed to open */ + return AVI_ERROR_FOUND; + } + + fseek(movie->fp, 0L, SEEK_END); + movi_size = (int)ftell(movie->fp); + + PUT_FCC("idx1", movie->fp); + PUT_FCCN((movie->index_entries * (movie->header->Streams + 1) * 16), movie->fp); + + for (temp = 0; temp < movie->index_entries * (movie->header->Streams + 1); temp++) { + awrite(movie, &movie->entries[temp], 1, sizeof(AviIndexEntry), movie->fp, AVI_INDEXE); + } + + temp = (int)ftell(movie->fp); + + fseek(movie->fp, AVI_RIFF_SOFF, SEEK_SET); + + PUT_FCCN((temp - 8L), movie->fp); + + fseek(movie->fp, movie->movi_offset, SEEK_SET); + + PUT_FCCN((movi_size - (movie->movi_offset + 4L)), movie->fp); + + fclose(movie->fp); + + for (i = 0; i < movie->header->Streams; i++) { + if (movie->streams && (movie->streams[i].sf != NULL)) { + MEM_freeN(movie->streams[i].sf); + } + } + + MEM_freeN(movie->header); + + if (movie->entries != NULL) { + MEM_freeN(movie->entries); + } + if (movie->streams != NULL) { + MEM_freeN(movie->streams); + } + if (movie->offset_table != NULL) { + MEM_freeN(movie->offset_table); + } + return AVI_ERROR_NONE; +} diff --git a/source/blender/io/avi/intern/avi_codecs.c b/source/blender/io/avi/intern/avi_codecs.c new file mode 100644 index 00000000000..15f498ac653 --- /dev/null +++ b/source/blender/io/avi/intern/avi_codecs.c @@ -0,0 +1,138 @@ +/* + * 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) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ + +/** \file + * \ingroup avi + * + * This is external code. Identify and convert different avi-files. + */ + +#include "AVI_avi.h" +#include "avi_intern.h" + +#include "avi_rgb.h" +#include "avi_mjpeg.h" +#include "avi_rgb32.h" + +void *avi_format_convert( + AviMovie *movie, int stream, void *buffer, AviFormat from, AviFormat to, size_t *size) +{ + if (from == to) { + return buffer; + } + + if (from != AVI_FORMAT_RGB24 && to != AVI_FORMAT_RGB24) { + return avi_format_convert( + movie, + stream, + avi_format_convert(movie, stream, buffer, from, AVI_FORMAT_RGB24, size), + AVI_FORMAT_RGB24, + to, + size); + } + + switch (to) { + case AVI_FORMAT_RGB24: + switch (from) { + case AVI_FORMAT_AVI_RGB: + buffer = avi_converter_from_avi_rgb(movie, stream, buffer, size); + break; + case AVI_FORMAT_MJPEG: + buffer = avi_converter_from_mjpeg(movie, stream, buffer, size); + break; + case AVI_FORMAT_RGB32: + buffer = avi_converter_from_rgb32(movie, stream, buffer, size); + break; + default: + break; + } + break; + case AVI_FORMAT_AVI_RGB: + buffer = avi_converter_to_avi_rgb(movie, stream, buffer, size); + break; + case AVI_FORMAT_MJPEG: + buffer = avi_converter_to_mjpeg(movie, stream, buffer, size); + break; + case AVI_FORMAT_RGB32: + buffer = avi_converter_to_rgb32(movie, stream, buffer, size); + break; + default: + break; + } + + return buffer; +} + +int avi_get_data_id(AviFormat format, int stream) +{ + char fcc[5]; + + if (avi_get_format_type(format) == FCC("vids")) { + sprintf(fcc, "%2.2ddc", stream); + } + else if (avi_get_format_type(format) == FCC("auds")) { + sprintf(fcc, "%2.2ddc", stream); + } + else { + return 0; + } + + return FCC(fcc); +} + +int avi_get_format_type(AviFormat format) +{ + switch (format) { + case AVI_FORMAT_RGB24: + case AVI_FORMAT_RGB32: + case AVI_FORMAT_AVI_RGB: + case AVI_FORMAT_MJPEG: + return FCC("vids"); + default: + return 0; + } +} + +int avi_get_format_fcc(AviFormat format) +{ + switch (format) { + case AVI_FORMAT_RGB24: + case AVI_FORMAT_RGB32: + case AVI_FORMAT_AVI_RGB: + return FCC("DIB "); + case AVI_FORMAT_MJPEG: + return FCC("MJPG"); + default: + return 0; + } +} + +int avi_get_format_compression(AviFormat format) +{ + switch (format) { + case AVI_FORMAT_RGB24: + case AVI_FORMAT_RGB32: + case AVI_FORMAT_AVI_RGB: + return 0; + case AVI_FORMAT_MJPEG: + return FCC("MJPG"); + default: + return 0; + } +} diff --git a/source/blender/io/avi/intern/avi_endian.c b/source/blender/io/avi/intern/avi_endian.c new file mode 100644 index 00000000000..56474e9e329 --- /dev/null +++ b/source/blender/io/avi/intern/avi_endian.c @@ -0,0 +1,203 @@ +/* + * 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) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ + +/** \file + * \ingroup avi + * + * This is external code. Streams bytes to output depending on the + * endianness of the system. + */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "AVI_avi.h" +#include "avi_endian.h" +#include "avi_intern.h" + +#ifdef __BIG_ENDIAN__ +# include "MEM_guardedalloc.h" +#endif + +#ifdef __BIG_ENDIAN__ + +/* copied from BLI_endian_switch_inline.h */ +static void invert(int *val) +{ + int tval = *val; + *val = ((tval >> 24)) | ((tval << 8) & 0x00ff0000) | ((tval >> 8) & 0x0000ff00) | ((tval << 24)); +} + +static void sinvert(short int *val) +{ + short tval = *val; + *val = (tval >> 8) | (tval << 8); +} + +static void Ichunk(AviChunk *chunk) +{ + invert(&chunk->fcc); + invert(&chunk->size); +} +#endif + +#ifdef __BIG_ENDIAN__ +static void Ilist(AviList *list) +{ + invert(&list->fcc); + invert(&list->size); + invert(&list->ids); +} + +static void Imainh(AviMainHeader *mainh) +{ + invert(&mainh->fcc); + invert(&mainh->size); + invert(&mainh->MicroSecPerFrame); + invert(&mainh->MaxBytesPerSec); + invert(&mainh->PaddingGranularity); + invert(&mainh->Flags); + invert(&mainh->TotalFrames); + invert(&mainh->InitialFrames); + invert(&mainh->Streams); + invert(&mainh->SuggestedBufferSize); + invert(&mainh->Width); + invert(&mainh->Height); + invert(&mainh->Reserved[0]); + invert(&mainh->Reserved[1]); + invert(&mainh->Reserved[2]); + invert(&mainh->Reserved[3]); +} + +static void Istreamh(AviStreamHeader *streamh) +{ + invert(&streamh->fcc); + invert(&streamh->size); + invert(&streamh->Type); + invert(&streamh->Handler); + invert(&streamh->Flags); + sinvert(&streamh->Priority); + sinvert(&streamh->Language); + invert(&streamh->InitialFrames); + invert(&streamh->Scale); + invert(&streamh->Rate); + invert(&streamh->Start); + invert(&streamh->Length); + invert(&streamh->SuggestedBufferSize); + invert(&streamh->Quality); + invert(&streamh->SampleSize); + sinvert(&streamh->left); + sinvert(&streamh->right); + sinvert(&streamh->top); + sinvert(&streamh->bottom); +} + +static void Ibitmaph(AviBitmapInfoHeader *bitmaph) +{ + invert(&bitmaph->fcc); + invert(&bitmaph->size); + invert(&bitmaph->Size); + invert(&bitmaph->Width); + invert(&bitmaph->Height); + sinvert(&bitmaph->Planes); + sinvert(&bitmaph->BitCount); + invert(&bitmaph->Compression); + invert(&bitmaph->SizeImage); + invert(&bitmaph->XPelsPerMeter); + invert(&bitmaph->YPelsPerMeter); + invert(&bitmaph->ClrUsed); + invert(&bitmaph->ClrImportant); +} + +static void Imjpegu(AviMJPEGUnknown *mjpgu) +{ + invert(&mjpgu->a); + invert(&mjpgu->b); + invert(&mjpgu->c); + invert(&mjpgu->d); + invert(&mjpgu->e); + invert(&mjpgu->f); + invert(&mjpgu->g); +} + +static void Iindexe(AviIndexEntry *indexe) +{ + invert(&indexe->ChunkId); + invert(&indexe->Flags); + invert(&indexe->Offset); + invert(&indexe->Size); +} +#endif /* __BIG_ENDIAN__ */ + +void awrite(AviMovie *movie, void *datain, int block, int size, FILE *fp, int type) +{ +#ifdef __BIG_ENDIAN__ + void *data; + + data = MEM_mallocN(size, "avi endian"); + + memcpy(data, datain, size); + + switch (type) { + case AVI_RAW: + fwrite(data, block, size, fp); + break; + case AVI_CHUNK: + Ichunk((AviChunk *)data); + fwrite(data, block, size, fp); + break; + case AVI_LIST: + Ilist((AviList *)data); + fwrite(data, block, size, fp); + break; + case AVI_MAINH: + Imainh((AviMainHeader *)data); + fwrite(data, block, size, fp); + break; + case AVI_STREAMH: + Istreamh((AviStreamHeader *)data); + fwrite(data, block, size, fp); + break; + case AVI_BITMAPH: + Ibitmaph((AviBitmapInfoHeader *)data); + if (size == sizeof(AviBitmapInfoHeader) + sizeof(AviMJPEGUnknown)) { + Imjpegu((AviMJPEGUnknown *)((char *)data + sizeof(AviBitmapInfoHeader))); + } + fwrite(data, block, size, fp); + break; + case AVI_MJPEGU: + Imjpegu((AviMJPEGUnknown *)data); + fwrite(data, block, size, fp); + break; + case AVI_INDEXE: + Iindexe((AviIndexEntry *)data); + fwrite(data, block, size, fp); + break; + default: + break; + } + + MEM_freeN(data); +#else /* __BIG_ENDIAN__ */ + (void)movie; /* unused */ + (void)type; /* unused */ + fwrite(datain, block, size, fp); +#endif /* __BIG_ENDIAN__ */ +} diff --git a/source/blender/io/avi/intern/avi_endian.h b/source/blender/io/avi/intern/avi_endian.h new file mode 100644 index 00000000000..d1253f488e7 --- /dev/null +++ b/source/blender/io/avi/intern/avi_endian.h @@ -0,0 +1,40 @@ +/* + * 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) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ + +/** \file + * \ingroup avi + * + * This is external code. + */ + +#ifndef __AVI_ENDIAN_H__ +#define __AVI_ENDIAN_H__ + +#define AVI_RAW 0 +#define AVI_CHUNK 1 +#define AVI_LIST 2 +#define AVI_MAINH 3 +#define AVI_STREAMH 4 +#define AVI_BITMAPH 5 +#define AVI_INDEXE 6 +#define AVI_MJPEGU 7 + +void awrite(AviMovie *movie, void *datain, int block, int size, FILE *fp, int type); + +#endif /* __AVI_ENDIAN_H__ */ diff --git a/source/blender/io/avi/intern/avi_intern.h b/source/blender/io/avi/intern/avi_intern.h new file mode 100644 index 00000000000..6ce91ce7f70 --- /dev/null +++ b/source/blender/io/avi/intern/avi_intern.h @@ -0,0 +1,65 @@ +/* + * 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) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ + +/** \file + * \ingroup avi + */ + +#ifndef __AVI_INTERN_H__ +#define __AVI_INTERN_H__ + +#include <stdio.h> /* for FILE */ + +unsigned int GET_FCC(FILE *fp); +unsigned int GET_TCC(FILE *fp); + +#define PUT_FCC(ch4, fp) \ + { \ + putc(ch4[0], fp); \ + putc(ch4[1], fp); \ + putc(ch4[2], fp); \ + putc(ch4[3], fp); \ + } \ + (void)0 + +#define PUT_FCCN(num, fp) \ + { \ + putc((num >> 0) & 0377, fp); \ + putc((num >> 8) & 0377, fp); \ + putc((num >> 16) & 0377, fp); \ + putc((num >> 24) & 0377, fp); \ + } \ + (void)0 + +#define PUT_TCC(ch2, fp) \ + { \ + putc(ch2[0], fp); \ + putc(ch2[1], fp); \ + } \ + (void)0 + +void *avi_format_convert( + AviMovie *movie, int stream, void *buffer, AviFormat from, AviFormat to, size_t *size); + +int avi_get_data_id(AviFormat format, int stream); +int avi_get_format_type(AviFormat format); +int avi_get_format_fcc(AviFormat format); +int avi_get_format_compression(AviFormat format); + +#endif diff --git a/source/blender/io/avi/intern/avi_mjpeg.c b/source/blender/io/avi/intern/avi_mjpeg.c new file mode 100644 index 00000000000..d4c7378964e --- /dev/null +++ b/source/blender/io/avi/intern/avi_mjpeg.c @@ -0,0 +1,547 @@ +/* + * 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) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ + +/** \file + * \ingroup avi + * + * This is external code. Converts between avi and mpeg/jpeg. + */ + +#include <stdlib.h> +#include <string.h> + +#include "AVI_avi.h" + +#include "MEM_guardedalloc.h" + +#include "IMB_imbuf.h" + +#include "jpeglib.h" +#include "jerror.h" + +#include "avi_mjpeg.h" + +static void jpegmemdestmgr_build(j_compress_ptr cinfo, unsigned char *buffer, size_t bufsize); +static void jpegmemsrcmgr_build(j_decompress_ptr dinfo, unsigned char *buffer, size_t bufsize); + +static size_t numbytes; + +static void add_huff_table(j_decompress_ptr dinfo, + JHUFF_TBL **htblptr, + const UINT8 *bits, + const UINT8 *val) +{ + if (*htblptr == NULL) { + *htblptr = jpeg_alloc_huff_table((j_common_ptr)dinfo); + } + + memcpy((*htblptr)->bits, bits, sizeof((*htblptr)->bits)); + memcpy((*htblptr)->huffval, val, sizeof((*htblptr)->huffval)); + + /* Initialize sent_table false so table will be written to JPEG file. */ + (*htblptr)->sent_table = false; +} + +/* Set up the standard Huffman tables (cf. JPEG standard section K.3) */ +/* IMPORTANT: these are only valid for 8-bit data precision! */ + +static void std_huff_tables(j_decompress_ptr dinfo) +{ + static const UINT8 bits_dc_luminance[17] = { + /* 0-base */ + 0, + 0, + 1, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + }; + static const UINT8 val_dc_luminance[] = { + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + }; + + static const UINT8 bits_dc_chrominance[17] = { + /* 0-base */ + 0, + 0, + 3, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + }; + static const UINT8 val_dc_chrominance[] = { + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + }; + + static const UINT8 bits_ac_luminance[17] = { + /* 0-base */ + 0, + 0, + 2, + 1, + 3, + 3, + 2, + 4, + 3, + 5, + 5, + 4, + 4, + 0, + 0, + 1, + 0x7d, + }; + static const UINT8 val_ac_luminance[] = { + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, + 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, + 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, + 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, + 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, + 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, + 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, + }; + static const UINT8 bits_ac_chrominance[17] = { + /* 0-base */ + 0, + 0, + 2, + 1, + 2, + 4, + 4, + 3, + 4, + 7, + 5, + 4, + 4, + 0, + 1, + 2, + 0x77, + }; + static const UINT8 val_ac_chrominance[] = { + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, + 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, + 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, + 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, + 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, + 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, + 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, + 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, + }; + + add_huff_table(dinfo, &dinfo->dc_huff_tbl_ptrs[0], bits_dc_luminance, val_dc_luminance); + add_huff_table(dinfo, &dinfo->ac_huff_tbl_ptrs[0], bits_ac_luminance, val_ac_luminance); + add_huff_table(dinfo, &dinfo->dc_huff_tbl_ptrs[1], bits_dc_chrominance, val_dc_chrominance); + add_huff_table(dinfo, &dinfo->ac_huff_tbl_ptrs[1], bits_ac_chrominance, val_ac_chrominance); +} + +static int Decode_JPEG(unsigned char *inBuffer, + unsigned char *outBuffer, + unsigned int width, + unsigned int height, + size_t bufsize) +{ + struct jpeg_decompress_struct dinfo; + struct jpeg_error_mgr jerr; + + (void)width; /* unused */ + + numbytes = 0; + + dinfo.err = jpeg_std_error(&jerr); + jpeg_create_decompress(&dinfo); + jpegmemsrcmgr_build(&dinfo, inBuffer, bufsize); + jpeg_read_header(&dinfo, true); + if (dinfo.dc_huff_tbl_ptrs[0] == NULL) { + std_huff_tables(&dinfo); + } + dinfo.out_color_space = JCS_RGB; + dinfo.dct_method = JDCT_IFAST; + + jpeg_start_decompress(&dinfo); + + size_t rowstride = dinfo.output_width * dinfo.output_components; + for (size_t y = 0; y < dinfo.output_height; y++) { + jpeg_read_scanlines(&dinfo, (JSAMPARRAY)&outBuffer, 1); + outBuffer += rowstride; + } + jpeg_finish_decompress(&dinfo); + + if (dinfo.output_height >= height) { + return 0; + } + + inBuffer += numbytes; + jpegmemsrcmgr_build(&dinfo, inBuffer, bufsize - numbytes); + + numbytes = 0; + jpeg_read_header(&dinfo, true); + if (dinfo.dc_huff_tbl_ptrs[0] == NULL) { + std_huff_tables(&dinfo); + } + + jpeg_start_decompress(&dinfo); + rowstride = dinfo.output_width * dinfo.output_components; + for (size_t y = 0; y < dinfo.output_height; y++) { + jpeg_read_scanlines(&dinfo, (JSAMPARRAY)&outBuffer, 1); + outBuffer += rowstride; + } + jpeg_finish_decompress(&dinfo); + jpeg_destroy_decompress(&dinfo); + + return 1; +} + +static void Compress_JPEG(int quality, + unsigned char *outbuffer, + const unsigned char *inBuffer, + int width, + int height, + size_t bufsize) +{ + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + unsigned char marker[60]; + + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_compress(&cinfo); + jpegmemdestmgr_build(&cinfo, outbuffer, bufsize); + + cinfo.image_width = width; + cinfo.image_height = height; + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + + jpeg_set_defaults(&cinfo); + jpeg_set_colorspace(&cinfo, JCS_YCbCr); + + jpeg_set_quality(&cinfo, quality, true); + + cinfo.dc_huff_tbl_ptrs[0]->sent_table = true; + cinfo.dc_huff_tbl_ptrs[1]->sent_table = true; + cinfo.ac_huff_tbl_ptrs[0]->sent_table = true; + cinfo.ac_huff_tbl_ptrs[1]->sent_table = true; + + cinfo.comp_info[0].component_id = 0; + cinfo.comp_info[0].v_samp_factor = 1; + cinfo.comp_info[1].component_id = 1; + cinfo.comp_info[2].component_id = 2; + + cinfo.write_JFIF_header = false; + + jpeg_start_compress(&cinfo, false); + + int i = 0; + marker[i++] = 'A'; + marker[i++] = 'V'; + marker[i++] = 'I'; + marker[i++] = '1'; + marker[i++] = 0; + while (i < 60) { + marker[i++] = 32; + } + + jpeg_write_marker(&cinfo, JPEG_APP0, marker, 60); + + i = 0; + while (i < 60) { + marker[i++] = 0; + } + + jpeg_write_marker(&cinfo, JPEG_COM, marker, 60); + + size_t rowstride = cinfo.image_width * cinfo.input_components; + for (size_t y = 0; y < cinfo.image_height; y++) { + jpeg_write_scanlines(&cinfo, (JSAMPARRAY)&inBuffer, 1); + inBuffer += rowstride; + } + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); +} + +static void interlace(unsigned char *to, unsigned char *from, int width, int height) +{ + size_t i, rowstride = width * 3; + + for (i = 0; i < height; i++) { + if (i & 1) { + memcpy(&to[i * rowstride], &from[(i / 2 + height / 2) * rowstride], rowstride); + } + else { + memcpy(&to[i * rowstride], &from[(i / 2) * rowstride], rowstride); + } + } +} + +static void deinterlace(int odd, unsigned char *to, unsigned char *from, int width, int height) +{ + size_t i, rowstride = width * 3; + + for (i = 0; i < height; i++) { + if ((i & 1) == odd) { + memcpy(&to[(i / 2 + height / 2) * rowstride], &from[i * rowstride], rowstride); + } + else { + memcpy(&to[(i / 2) * rowstride], &from[i * rowstride], rowstride); + } + } +} + +void *avi_converter_from_mjpeg(AviMovie *movie, int stream, unsigned char *buffer, size_t *size) +{ + int deint; + unsigned char *buf; + + (void)stream; /* unused */ + + buf = imb_alloc_pixels(movie->header->Height, + movie->header->Width, + 3, + sizeof(unsigned char), + "avi.avi_converter_from_mjpeg 1"); + if (!buf) { + return NULL; + } + + deint = Decode_JPEG(buffer, buf, movie->header->Width, movie->header->Height, *size); + + MEM_freeN(buffer); + + if (deint) { + buffer = imb_alloc_pixels(movie->header->Height, + movie->header->Width, + 3, + sizeof(unsigned char), + "avi.avi_converter_from_mjpeg 2"); + if (buffer) { + interlace(buffer, buf, movie->header->Width, movie->header->Height); + } + MEM_freeN(buf); + + buf = buffer; + } + + return buf; +} + +void *avi_converter_to_mjpeg(AviMovie *movie, int stream, unsigned char *buffer, size_t *size) +{ + unsigned char *buf; + size_t bufsize = *size; + + numbytes = 0; + *size = 0; + + buf = imb_alloc_pixels(movie->header->Height, + movie->header->Width, + 3, + sizeof(unsigned char), + "avi.avi_converter_to_mjpeg 1"); + if (!buf) { + return NULL; + } + + if (!movie->interlace) { + Compress_JPEG(movie->streams[stream].sh.Quality / 100, + buf, + buffer, + movie->header->Width, + movie->header->Height, + bufsize); + *size += numbytes; + } + else { + deinterlace(movie->odd_fields, buf, buffer, movie->header->Width, movie->header->Height); + MEM_freeN(buffer); + + buffer = buf; + buf = imb_alloc_pixels(movie->header->Height, + movie->header->Width, + 3, + sizeof(unsigned char), + "avi.avi_converter_to_mjpeg 1"); + + if (buf) { + Compress_JPEG(movie->streams[stream].sh.Quality / 100, + buf, + buffer, + movie->header->Width, + movie->header->Height / 2, + bufsize / 2); + *size += numbytes; + numbytes = 0; + Compress_JPEG(movie->streams[stream].sh.Quality / 100, + buf + *size, + buffer + + (size_t)(movie->header->Height / 2) * (size_t)movie->header->Width * 3, + movie->header->Width, + movie->header->Height / 2, + bufsize / 2); + *size += numbytes; + } + } + + MEM_freeN(buffer); + return buf; +} + +/* Compression from memory */ + +static void jpegmemdestmgr_init_destination(j_compress_ptr cinfo) +{ + (void)cinfo; /* unused */ +} + +static boolean jpegmemdestmgr_empty_output_buffer(j_compress_ptr cinfo) +{ + (void)cinfo; /* unused */ + return true; +} + +static void jpegmemdestmgr_term_destination(j_compress_ptr cinfo) +{ + numbytes -= cinfo->dest->free_in_buffer; + + MEM_freeN(cinfo->dest); +} + +static void jpegmemdestmgr_build(j_compress_ptr cinfo, unsigned char *buffer, size_t bufsize) +{ + cinfo->dest = MEM_mallocN(sizeof(*(cinfo->dest)), "avi.jpegmemdestmgr_build"); + + cinfo->dest->init_destination = jpegmemdestmgr_init_destination; + cinfo->dest->empty_output_buffer = jpegmemdestmgr_empty_output_buffer; + cinfo->dest->term_destination = jpegmemdestmgr_term_destination; + + cinfo->dest->next_output_byte = buffer; + cinfo->dest->free_in_buffer = bufsize; + + numbytes = bufsize; +} + +/* Decompression from memory */ + +static void jpegmemsrcmgr_init_source(j_decompress_ptr dinfo) +{ + (void)dinfo; +} + +static boolean jpegmemsrcmgr_fill_input_buffer(j_decompress_ptr dinfo) +{ + unsigned char *buf = (unsigned char *)dinfo->src->next_input_byte - 2; + + /* if we get called, must have run out of data */ + WARNMS(dinfo, JWRN_JPEG_EOF); + + buf[0] = (JOCTET)0xFF; + buf[1] = (JOCTET)JPEG_EOI; + + dinfo->src->next_input_byte = buf; + dinfo->src->bytes_in_buffer = 2; + + return true; +} + +static void jpegmemsrcmgr_skip_input_data(j_decompress_ptr dinfo, long skipcnt) +{ + if (dinfo->src->bytes_in_buffer < skipcnt) { + skipcnt = dinfo->src->bytes_in_buffer; + } + + dinfo->src->next_input_byte += skipcnt; + dinfo->src->bytes_in_buffer -= skipcnt; +} + +static void jpegmemsrcmgr_term_source(j_decompress_ptr dinfo) +{ + numbytes -= dinfo->src->bytes_in_buffer; + + MEM_freeN(dinfo->src); +} + +static void jpegmemsrcmgr_build(j_decompress_ptr dinfo, unsigned char *buffer, size_t bufsize) +{ + dinfo->src = MEM_mallocN(sizeof(*(dinfo->src)), "avi.jpegmemsrcmgr_build"); + + dinfo->src->init_source = jpegmemsrcmgr_init_source; + dinfo->src->fill_input_buffer = jpegmemsrcmgr_fill_input_buffer; + dinfo->src->skip_input_data = jpegmemsrcmgr_skip_input_data; + dinfo->src->resync_to_restart = jpeg_resync_to_restart; + dinfo->src->term_source = jpegmemsrcmgr_term_source; + + dinfo->src->bytes_in_buffer = bufsize; + dinfo->src->next_input_byte = buffer; + + numbytes = bufsize; +} diff --git a/source/blender/io/avi/intern/avi_mjpeg.h b/source/blender/io/avi/intern/avi_mjpeg.h new file mode 100644 index 00000000000..30e46bf1d0c --- /dev/null +++ b/source/blender/io/avi/intern/avi_mjpeg.h @@ -0,0 +1,30 @@ +/* + * 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) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ + +/** \file + * \ingroup avi + */ + +#ifndef __AVI_MJPEG_H__ +#define __AVI_MJPEG_H__ + +void *avi_converter_from_mjpeg(AviMovie *movie, int stream, unsigned char *buffer, size_t *size); +void *avi_converter_to_mjpeg(AviMovie *movie, int stream, unsigned char *buffer, size_t *size); + +#endif /* __AVI_MJPEG_H__ */ diff --git a/source/blender/io/avi/intern/avi_options.c b/source/blender/io/avi/intern/avi_options.c new file mode 100644 index 00000000000..65db8c19397 --- /dev/null +++ b/source/blender/io/avi/intern/avi_options.c @@ -0,0 +1,148 @@ +/* + * 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) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ + +/** \file + * \ingroup avi + * + * This is external code. Sets some compression related options + * (width, height quality, framerate). + */ + +#include "AVI_avi.h" +#include "avi_intern.h" +#include "avi_endian.h" + +#ifdef WIN32 +# include "BLI_winstuff.h" +#endif + +/* avi_set_compress_options gets its own file... now don't WE feel important? */ + +AviError AVI_set_compress_option( + AviMovie *movie, int option_type, int stream, AviOption option, void *opt_data) +{ + int i; + int useconds; + + (void)stream; /* unused */ + + if (movie->header->TotalFrames != 0) { + /* Can't change params after we have already started writing frames. */ + return AVI_ERROR_OPTION; + } + + switch (option_type) { + case AVI_OPTION_TYPE_MAIN: + switch (option) { + case AVI_OPTION_WIDTH: + movie->header->Width = *((int *)opt_data); + movie->header->SuggestedBufferSize = movie->header->Width * movie->header->Height * 3; + + for (i = 0; i < movie->header->Streams; i++) { + if (avi_get_format_type(movie->streams[i].format) == FCC("vids")) { + ((AviBitmapInfoHeader *)movie->streams[i].sf)->Width = *((int *)opt_data); + movie->streams[i].sh.SuggestedBufferSize = movie->header->SuggestedBufferSize; + movie->streams[i].sh.right = *((int *)opt_data); + ((AviBitmapInfoHeader *)movie->streams[i].sf)->SizeImage = + movie->header->SuggestedBufferSize; + fseek(movie->fp, movie->offset_table[1 + i * 2 + 1], SEEK_SET); + awrite(movie, + movie->streams[i].sf, + 1, + movie->streams[i].sf_size, + movie->fp, + AVI_BITMAPH); + } + } + + break; + + case AVI_OPTION_HEIGHT: + movie->header->Height = *((int *)opt_data); + movie->header->SuggestedBufferSize = movie->header->Width * movie->header->Height * 3; + + for (i = 0; i < movie->header->Streams; i++) { + if (avi_get_format_type(movie->streams[i].format) == FCC("vids")) { + ((AviBitmapInfoHeader *)movie->streams[i].sf)->Height = *((int *)opt_data); + movie->streams[i].sh.SuggestedBufferSize = movie->header->SuggestedBufferSize; + movie->streams[i].sh.bottom = *((int *)opt_data); + ((AviBitmapInfoHeader *)movie->streams[i].sf)->SizeImage = + movie->header->SuggestedBufferSize; + fseek(movie->fp, movie->offset_table[1 + i * 2 + 1], SEEK_SET); + awrite(movie, + movie->streams[i].sf, + 1, + movie->streams[i].sf_size, + movie->fp, + AVI_BITMAPH); + } + } + + break; + + case AVI_OPTION_QUALITY: + for (i = 0; i < movie->header->Streams; i++) { + if (avi_get_format_type(movie->streams[i].format) == FCC("vids")) { + movie->streams[i].sh.Quality = (*((int *)opt_data)) * 100; + fseek(movie->fp, movie->offset_table[1 + i * 2 + 1], SEEK_SET); + awrite(movie, + movie->streams[i].sf, + 1, + movie->streams[i].sf_size, + movie->fp, + AVI_BITMAPH); + } + } + break; + + case AVI_OPTION_FRAMERATE: + useconds = (int)(1000000 / (*((double *)opt_data))); + if (useconds) { + movie->header->MicroSecPerFrame = useconds; + } + + for (i = 0; i < movie->header->Streams; i++) { + if (avi_get_format_type(movie->streams[i].format) == FCC("vids")) { + movie->streams[i].sh.Scale = movie->header->MicroSecPerFrame; + fseek(movie->fp, movie->offset_table[1 + i * 2 + 1], SEEK_SET); + awrite(movie, + movie->streams[i].sf, + 1, + movie->streams[i].sf_size, + movie->fp, + AVI_BITMAPH); + } + } + break; + } + + fseek(movie->fp, movie->offset_table[0], SEEK_SET); + awrite(movie, movie->header, 1, sizeof(AviMainHeader), movie->fp, AVI_MAINH); + + break; + case AVI_OPTION_TYPE_STRH: + break; + case AVI_OPTION_TYPE_STRF: + break; + default: + return AVI_ERROR_OPTION; + } + + return AVI_ERROR_NONE; +} diff --git a/source/blender/io/avi/intern/avi_rgb.c b/source/blender/io/avi/intern/avi_rgb.c new file mode 100644 index 00000000000..d449556e79b --- /dev/null +++ b/source/blender/io/avi/intern/avi_rgb.c @@ -0,0 +1,154 @@ +/* + * 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) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ + +/** \file + * \ingroup avi + * + * This is external code. Converts rgb-type avi-s. + */ + +#include <stdlib.h> +#include <string.h> + +#include "MEM_guardedalloc.h" + +#include "AVI_avi.h" +#include "avi_rgb.h" + +#include "IMB_imbuf.h" + +/* implementation */ + +void *avi_converter_from_avi_rgb(AviMovie *movie, int stream, unsigned char *buffer, size_t *size) +{ + unsigned char *buf; + AviBitmapInfoHeader *bi; + short bits = 32; + + (void)size; /* unused */ + + bi = (AviBitmapInfoHeader *)movie->streams[stream].sf; + if (bi) { + bits = bi->BitCount; + } + + if (bits == 16) { + unsigned short *pxl; + unsigned char *to; +#ifdef __BIG_ENDIAN__ + unsigned char *pxla; +#endif + + buf = imb_alloc_pixels( + movie->header->Height, movie->header->Width, 3, sizeof(unsigned char), "fromavirgbbuf"); + + if (buf) { + size_t y = movie->header->Height; + to = buf; + + while (y--) { + pxl = (unsigned short *)(buffer + y * movie->header->Width * 2); + +#ifdef __BIG_ENDIAN__ + pxla = (unsigned char *)pxl; +#endif + + size_t x = movie->header->Width; + while (x--) { +#ifdef __BIG_ENDIAN__ + int i = pxla[0]; + pxla[0] = pxla[1]; + pxla[1] = i; + + pxla += 2; +#endif + + *(to++) = ((*pxl >> 10) & 0x1f) * 8; + *(to++) = ((*pxl >> 5) & 0x1f) * 8; + *(to++) = (*pxl & 0x1f) * 8; + pxl++; + } + } + } + + MEM_freeN(buffer); + + return buf; + } + else { + buf = imb_alloc_pixels( + movie->header->Height, movie->header->Width, 3, sizeof(unsigned char), "fromavirgbbuf"); + + if (buf) { + size_t rowstride = movie->header->Width * 3; + if ((bits != 16) && (movie->header->Width % 2)) { + rowstride++; + } + + for (size_t y = 0; y < movie->header->Height; y++) { + memcpy(&buf[y * movie->header->Width * 3], + &buffer[((movie->header->Height - 1) - y) * rowstride], + movie->header->Width * 3); + } + + for (size_t y = 0; y < (size_t)movie->header->Height * (size_t)movie->header->Width * 3; + y += 3) { + int i = buf[y]; + buf[y] = buf[y + 2]; + buf[y + 2] = i; + } + } + + MEM_freeN(buffer); + + return buf; + } +} + +void *avi_converter_to_avi_rgb(AviMovie *movie, int stream, unsigned char *buffer, size_t *size) +{ + unsigned char *buf; + + (void)stream; /* unused */ + + size_t rowstride = movie->header->Width * 3; + /* AVI files has uncompressed lines 4-byte aligned */ + rowstride = (rowstride + 3) & ~3; + + *size = movie->header->Height * rowstride; + buf = MEM_mallocN(*size, "toavirgbbuf"); + + for (size_t y = 0; y < movie->header->Height; y++) { + memcpy(&buf[y * rowstride], + &buffer[((movie->header->Height - 1) - y) * movie->header->Width * 3], + movie->header->Width * 3); + } + + for (size_t y = 0; y < movie->header->Height; y++) { + for (size_t x = 0; x < movie->header->Width * 3; x += 3) { + int i = buf[y * rowstride + x]; + buf[y * rowstride + x] = buf[y * rowstride + x + 2]; + buf[y * rowstride + x + 2] = i; + } + } + + MEM_freeN(buffer); + + return buf; +} diff --git a/source/blender/io/avi/intern/avi_rgb.h b/source/blender/io/avi/intern/avi_rgb.h new file mode 100644 index 00000000000..7c8ce590d27 --- /dev/null +++ b/source/blender/io/avi/intern/avi_rgb.h @@ -0,0 +1,30 @@ +/* + * 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) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ + +/** \file + * \ingroup avi + */ + +#ifndef __AVI_RGB_H__ +#define __AVI_RGB_H__ + +void *avi_converter_from_avi_rgb(AviMovie *movie, int stream, unsigned char *buffer, size_t *size); +void *avi_converter_to_avi_rgb(AviMovie *movie, int stream, unsigned char *buffer, size_t *size); + +#endif /* __AVI_RGB_H__ */ diff --git a/source/blender/io/avi/intern/avi_rgb32.c b/source/blender/io/avi/intern/avi_rgb32.c new file mode 100644 index 00000000000..3efa4814c70 --- /dev/null +++ b/source/blender/io/avi/intern/avi_rgb32.c @@ -0,0 +1,94 @@ +/* + * 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) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ + +/** \file + * \ingroup avi + * + * This is external code. Converts between rgb32 and avi. + */ + +#include <stdlib.h> +#include <string.h> + +#include "MEM_guardedalloc.h" + +#include "IMB_imbuf.h" + +#include "AVI_avi.h" +#include "avi_rgb32.h" + +void *avi_converter_from_rgb32(AviMovie *movie, int stream, unsigned char *buffer, size_t *size) +{ + unsigned char *buf; + + (void)stream; /* unused */ + + *size = (size_t)movie->header->Height * (size_t)movie->header->Width * 3; + buf = imb_alloc_pixels( + movie->header->Height, movie->header->Width, 3, sizeof(unsigned char), "fromrgb32buf"); + if (!buf) { + return NULL; + } + + size_t rowstridea = movie->header->Width * 3; + size_t rowstrideb = movie->header->Width * 4; + + for (size_t y = 0; y < movie->header->Height; y++) { + for (size_t x = 0; x < movie->header->Width; x++) { + buf[y * rowstridea + x * 3 + 0] = buffer[y * rowstrideb + x * 4 + 3]; + buf[y * rowstridea + x * 3 + 1] = buffer[y * rowstrideb + x * 4 + 2]; + buf[y * rowstridea + x * 3 + 2] = buffer[y * rowstrideb + x * 4 + 1]; + } + } + + MEM_freeN(buffer); + + return buf; +} + +void *avi_converter_to_rgb32(AviMovie *movie, int stream, unsigned char *buffer, size_t *size) +{ + unsigned char *buf; + unsigned char *to, *from; + + (void)stream; /* unused */ + + *size = (size_t)movie->header->Height * (size_t)movie->header->Width * 4; + buf = imb_alloc_pixels( + movie->header->Height, movie->header->Width, 4, sizeof(unsigned char), "torgb32buf"); + if (!buf) { + return NULL; + } + + memset(buf, 255, *size); + + to = buf; + from = buffer; + size_t i = (size_t)movie->header->Height * (size_t)movie->header->Width; + + while (i--) { + memcpy(to, from, 3); + to += 4; + from += 3; + } + + MEM_freeN(buffer); + + return buf; +} diff --git a/source/blender/io/avi/intern/avi_rgb32.h b/source/blender/io/avi/intern/avi_rgb32.h new file mode 100644 index 00000000000..eb4b9ca4e21 --- /dev/null +++ b/source/blender/io/avi/intern/avi_rgb32.h @@ -0,0 +1,30 @@ +/* + * 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) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ + +/** \file + * \ingroup avi + */ + +#ifndef __AVI_RGB32_H__ +#define __AVI_RGB32_H__ + +void *avi_converter_from_rgb32(AviMovie *movie, int stream, unsigned char *buffer, size_t *size); +void *avi_converter_to_rgb32(AviMovie *movie, int stream, unsigned char *buffer, size_t *size); + +#endif /* __AVI_RGB32_H__ */ diff --git a/source/blender/io/collada/AnimationClipExporter.cpp b/source/blender/io/collada/AnimationClipExporter.cpp new file mode 100644 index 00000000000..5868c24e6cd --- /dev/null +++ b/source/blender/io/collada/AnimationClipExporter.cpp @@ -0,0 +1,50 @@ +/* + * 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. + */ + +#include "GeometryExporter.h" +#include "AnimationClipExporter.h" +#include "MaterialExporter.h" + +void AnimationClipExporter::exportAnimationClips(Scene *sce) +{ + openLibrary(); + std::map<std::string, COLLADASW::ColladaAnimationClip *> clips; + + std::vector<std::vector<std::string>>::iterator anim_meta_entry; + for (anim_meta_entry = anim_meta.begin(); anim_meta_entry != anim_meta.end(); + ++anim_meta_entry) { + std::vector<std::string> entry = *anim_meta_entry; + std::string action_id = entry[0]; + std::string action_name = entry[1]; + + std::map<std::string, COLLADASW::ColladaAnimationClip *>::iterator it = clips.find( + action_name); + if (it == clips.end()) { + COLLADASW::ColladaAnimationClip *clip = new COLLADASW::ColladaAnimationClip(action_name); + clips[action_name] = clip; + } + COLLADASW::ColladaAnimationClip *clip = clips[action_name]; + clip->setInstancedAnimation(action_id); + } + + std::map<std::string, COLLADASW::ColladaAnimationClip *>::iterator clips_it; + for (clips_it = clips.begin(); clips_it != clips.end(); clips_it++) { + COLLADASW::ColladaAnimationClip *clip = (COLLADASW::ColladaAnimationClip *)clips_it->second; + addAnimationClip(*clip); + } + + closeLibrary(); +} diff --git a/source/blender/io/collada/AnimationClipExporter.h b/source/blender/io/collada/AnimationClipExporter.h new file mode 100644 index 00000000000..25c69fe6b93 --- /dev/null +++ b/source/blender/io/collada/AnimationClipExporter.h @@ -0,0 +1,51 @@ +/* + * 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. + */ + +#ifndef __ANIMATIONCLIPEXPORTER_H__ +#define __ANIMATIONCLIPEXPORTER_H__ + +#include <stdlib.h> +#include <stdio.h> +#include <math.h> + +#include "COLLADASWLibraryAnimationClips.h" + +class AnimationClipExporter : COLLADASW::LibraryAnimationClips { + private: + Depsgraph *depsgraph; + Scene *scene; + COLLADASW::StreamWriter *sw; + BCExportSettings &export_settings; + std::vector<std::vector<std::string>> anim_meta; + + public: + AnimationClipExporter(Depsgraph *depsgraph, + COLLADASW::StreamWriter *sw, + BCExportSettings &export_settings, + std::vector<std::vector<std::string>> anim_meta) + : COLLADASW::LibraryAnimationClips(sw), + depsgraph(depsgraph), + scene(nullptr), + sw(sw), + export_settings(export_settings), + anim_meta(anim_meta) + { + } + + void exportAnimationClips(Scene *sce); +}; + +#endif /* __ANIMATIONCLIPEXPORTER_H__ */ diff --git a/source/blender/io/collada/AnimationExporter.cpp b/source/blender/io/collada/AnimationExporter.cpp new file mode 100644 index 00000000000..cd4319e3101 --- /dev/null +++ b/source/blender/io/collada/AnimationExporter.cpp @@ -0,0 +1,877 @@ +/* + * 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 collada + */ + +#include "GeometryExporter.h" +#include "AnimationExporter.h" +#include "AnimationClipExporter.h" +#include "BCAnimationSampler.h" +#include "MaterialExporter.h" +#include "collada_utils.h" + +std::string EMPTY_STRING; + +std::string AnimationExporter::get_axis_name(std::string channel_type, int id) +{ + + static std::map<std::string, std::vector<std::string>> BC_COLLADA_AXIS_FROM_TYPE = { + {"color", {"R", "G", "B"}}, + {"specular_color", {"R", "G", "B"}}, + {"diffuse_color", {"R", "G", "B"}}, + {"alpha", {"R", "G", "B"}}, + {"scale", {"X", "Y", "Z"}}, + {"location", {"X", "Y", "Z"}}, + {"rotation_euler", {"X", "Y", "Z"}}}; + + std::map<std::string, std::vector<std::string>>::const_iterator it; + + it = BC_COLLADA_AXIS_FROM_TYPE.find(channel_type); + if (it == BC_COLLADA_AXIS_FROM_TYPE.end()) { + return ""; + } + + const std::vector<std::string> &subchannel = it->second; + if (id >= subchannel.size()) { + return ""; + } + return subchannel[id]; +} + +bool AnimationExporter::open_animation_container(bool has_container, Object *ob) +{ + if (!has_container) { + char anim_id[200]; + sprintf(anim_id, "action_container-%s", translate_id(id_name(ob)).c_str()); + openAnimation(anim_id, encode_xml(id_name(ob))); + } + return true; +} + +void AnimationExporter::openAnimationWithClip(std::string action_id, std::string action_name) +{ + std::vector<std::string> anim_meta_entry; + anim_meta_entry.push_back(translate_id(action_id)); + anim_meta_entry.push_back(action_name); + anim_meta.push_back(anim_meta_entry); + + openAnimation(translate_id(action_id), action_name); +} + +void AnimationExporter::close_animation_container(bool has_container) +{ + if (has_container) { + closeAnimation(); + } +} + +bool AnimationExporter::exportAnimations() +{ + Scene *sce = export_settings.get_scene(); + + LinkNode *export_set = this->export_settings.get_export_set(); + bool has_anim_data = bc_has_animations(sce, export_set); + int animation_count = 0; + if (has_anim_data) { + + BCObjectSet animated_subset; + BCAnimationSampler::get_animated_from_export_set(animated_subset, *export_set); + animation_count = animated_subset.size(); + BCAnimationSampler animation_sampler(export_settings, animated_subset); + + try { + animation_sampler.sample_scene(export_settings, /*keyframe_at_end = */ true); + + openLibrary(); + + BCObjectSet::iterator it; + for (it = animated_subset.begin(); it != animated_subset.end(); ++it) { + Object *ob = *it; + exportAnimation(ob, animation_sampler); + } + } + catch (std::invalid_argument &iae) { + fprintf(stderr, "Animation export interrupted"); + fprintf(stderr, "Exception was: %s", iae.what()); + } + + closeLibrary(); + +#if 0 + /* TODO: If all actions shall be exported, we need to call the + * AnimationClipExporter which will figure out which actions + * need to be exported for which objects + */ + if (this->export_settings->include_all_actions) { + AnimationClipExporter ace(eval_ctx, sw, export_settings, anim_meta); + ace.exportAnimationClips(sce); + } +#endif + } + return animation_count; +} + +/* called for each exported object */ +void AnimationExporter::exportAnimation(Object *ob, BCAnimationSampler &sampler) +{ + bool container_is_open = false; + + /* Transform animations (trans, rot, scale). */ + container_is_open = open_animation_container(container_is_open, ob); + + /* Now take care of the Object Animations + * Note: For Armatures the skeletal animation has already been exported (see above) + * However Armatures also can have Object animation. + */ + bool export_as_matrix = this->export_settings.get_animation_transformation_type() == + BC_TRANSFORMATION_TYPE_MATRIX; + + if (export_as_matrix) { + /* export all transform_curves as one single matrix animation */ + export_matrix_animation(ob, sampler); + } + + export_curve_animation_set(ob, sampler, export_as_matrix); + + if (ob->type == OB_ARMATURE && export_as_matrix) { + +#ifdef WITH_MORPH_ANIMATION + /* TODO: This needs to be handled by extra profiles, postponed for now */ + export_morph_animation(ob); +#endif + + /* Export skeletal animation (if any) */ + bArmature *arm = (bArmature *)ob->data; + for (Bone *root_bone = (Bone *)arm->bonebase.first; root_bone; root_bone = root_bone->next) { + export_bone_animations_recursive(ob, root_bone, sampler); + } + } + + close_animation_container(container_is_open); +} + +/* + * Export all animation FCurves of an Object. + * + * Note: This uses the keyframes as sample points, + * and exports "baked keyframes" while keeping the tangent information + * of the FCurves intact. This works for simple cases, but breaks + * especially when negative scales are involved in the animation. + * And when parent inverse matrices are involved (when exporting + * object hierarchies) + */ +void AnimationExporter::export_curve_animation_set(Object *ob, + BCAnimationSampler &sampler, + bool export_as_matrix) +{ + BCAnimationCurveMap *curves = sampler.get_curves(ob); + bool keep_flat_curves = this->export_settings.get_keep_flat_curves(); + + BCAnimationCurveMap::iterator it; + for (it = curves->begin(); it != curves->end(); ++it) { + BCAnimationCurve &curve = *it->second; + std::string channel_type = curve.get_channel_type(); + if (channel_type == "rotation_quaternion") { + /* Can not export Quaternion animation in Collada as far as i know) + * Maybe automatically convert to euler rotation? + * Discard for now. */ + continue; + } + + if (export_as_matrix && curve.is_transform_curve()) { + /* All Transform curves will be exported within a single matrix animation, + * see export_matrix_animation() + * No need to export the curves here again. + */ + continue; + } + + if (!keep_flat_curves && !curve.is_animated()) { + continue; + } + + BCAnimationCurve *mcurve = get_modified_export_curve(ob, curve, *curves); + if (mcurve) { + export_curve_animation(ob, *mcurve); + delete mcurve; + } + else { + export_curve_animation(ob, curve); + } + } +} + +void AnimationExporter::export_matrix_animation(Object *ob, BCAnimationSampler &sampler) +{ + bool keep_flat_curves = this->export_settings.get_keep_flat_curves(); + + std::vector<float> frames; + sampler.get_object_frames(frames, ob); + if (frames.size() > 0) { + BCMatrixSampleMap samples; + bool is_animated = sampler.get_object_samples(samples, ob); + if (keep_flat_curves || is_animated) { + bAction *action = bc_getSceneObjectAction(ob); + std::string name = encode_xml(id_name(ob)); + std::string action_name = (action == NULL) ? name + "-action" : id_name(action); + std::string channel_type = "transform"; + std::string axis = ""; + std::string id = bc_get_action_id(action_name, name, channel_type, axis); + + std::string target = translate_id(name) + '/' + channel_type; + + BC_global_rotation_type global_rotation_type = get_global_rotation_type(ob); + export_collada_matrix_animation( + id, name, target, frames, samples, global_rotation_type, ob->parentinv); + } + } +} + +BC_global_rotation_type AnimationExporter::get_global_rotation_type(Object *ob) +{ + bool is_export_root = this->export_settings.is_export_root(ob); + if (!is_export_root) { + return BC_NO_ROTATION; + } + + bool apply_global_rotation = this->export_settings.get_apply_global_orientation(); + + return (apply_global_rotation) ? BC_DATA_ROTATION : BC_OBJECT_ROTATION; +} + +/* Write bone animations in transform matrix sources. */ +void AnimationExporter::export_bone_animations_recursive(Object *ob, + Bone *bone, + BCAnimationSampler &sampler) +{ + bool keep_flat_curves = this->export_settings.get_keep_flat_curves(); + + std::vector<float> frames; + sampler.get_bone_frames(frames, ob, bone); + + if (frames.size()) { + BCMatrixSampleMap samples; + bool is_animated = sampler.get_bone_samples(samples, ob, bone); + if (keep_flat_curves || is_animated) { + export_bone_animation(ob, bone, frames, samples); + } + } + + for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) { + export_bone_animations_recursive(ob, child, sampler); + } +} + +/** + * In some special cases the exported Curve needs to be replaced + * by a modified curve (for collada purposes) + * This method checks if a conversion is necessary and if applicable + * returns a pointer to the modified BCAnimationCurve. + * IMPORTANT: the modified curve must be deleted by the caller when no longer needed + * if no conversion is needed this method returns a NULL; + */ +BCAnimationCurve *AnimationExporter::get_modified_export_curve(Object *ob, + BCAnimationCurve &curve, + BCAnimationCurveMap &curves) +{ + std::string channel_type = curve.get_channel_type(); + BCAnimationCurve *mcurve = NULL; + if (channel_type == "lens") { + + /* Create an xfov curve */ + + BCCurveKey key(BC_ANIMATION_TYPE_CAMERA, "xfov", 0); + mcurve = new BCAnimationCurve(key, ob); + + /* now tricky part: transform the fcurve */ + BCValueMap lens_values; + curve.get_value_map(lens_values); + + BCAnimationCurve *sensor_curve = NULL; + BCCurveKey sensor_key(BC_ANIMATION_TYPE_CAMERA, "sensor_width", 0); + BCAnimationCurveMap::iterator cit = curves.find(sensor_key); + if (cit != curves.end()) { + sensor_curve = cit->second; + } + + BCValueMap::const_iterator vit; + for (vit = lens_values.begin(); vit != lens_values.end(); ++vit) { + int frame = vit->first; + float lens_value = vit->second; + + float sensor_value; + if (sensor_curve) { + sensor_value = sensor_curve->get_value(frame); + } + else { + sensor_value = ((Camera *)ob->data)->sensor_x; + } + float value = RAD2DEGF(focallength_to_fov(lens_value, sensor_value)); + mcurve->add_value(value, frame); + } + /* to reset the handles */ + mcurve->clean_handles(); + } + return mcurve; +} + +void AnimationExporter::export_curve_animation(Object *ob, BCAnimationCurve &curve) +{ + std::string channel_target = curve.get_channel_target(); + + /* + * Some curves can not be exported as is and need some conversion + * For more information see implementation of get_modified_export_curve() + * note: if mcurve is not NULL then it must be deleted at end of this method; + */ + + int channel_index = curve.get_channel_index(); + /* RGB or XYZ or "" */ + std::string channel_type = curve.get_channel_type(); + std::string axis = get_axis_name(channel_type, channel_index); + + std::string action_name; + bAction *action = bc_getSceneObjectAction(ob); + action_name = (action) ? id_name(action) : "constraint_anim"; + + const std::string curve_name = encode_xml(curve.get_animation_name(ob)); + std::string id = bc_get_action_id(action_name, curve_name, channel_target, axis, "."); + + std::string collada_target = translate_id(curve_name); + + if (curve.is_of_animation_type(BC_ANIMATION_TYPE_MATERIAL)) { + int material_index = curve.get_subindex(); + Material *ma = BKE_object_material_get(ob, material_index + 1); + if (ma) { + collada_target = translate_id(id_name(ma)) + "-effect/common/" + + get_collada_sid(curve, axis); + } + } + else { + collada_target += "/" + get_collada_sid(curve, axis); + } + + BC_global_rotation_type global_rotation_type = get_global_rotation_type(ob); + export_collada_curve_animation( + id, curve_name, collada_target, axis, curve, global_rotation_type); +} + +void AnimationExporter::export_bone_animation(Object *ob, + Bone *bone, + BCFrames &frames, + BCMatrixSampleMap &samples) +{ + bAction *action = bc_getSceneObjectAction(ob); + std::string bone_name(bone->name); + std::string name = encode_xml(id_name(ob)); + std::string id = bc_get_action_id(id_name(action), name, bone_name, "pose_matrix"); + std::string target = translate_id(id_name(ob) + "_" + bone_name) + "/transform"; + + BC_global_rotation_type global_rotation_type = get_global_rotation_type(ob); + export_collada_matrix_animation( + id, name, target, frames, samples, global_rotation_type, ob->parentinv); +} + +bool AnimationExporter::is_bone_deform_group(Bone *bone) +{ + bool is_def; + /* Check if current bone is deform */ + if ((bone->flag & BONE_NO_DEFORM) == 0) { + return true; + } + /* Check child bones */ + else { + for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) { + /* loop through all the children until deform bone is found, and then return */ + is_def = is_bone_deform_group(child); + if (is_def) { + return true; + } + } + } + /* no deform bone found in children also */ + return false; +} + +void AnimationExporter::export_collada_curve_animation( + std::string id, + std::string name, + std::string collada_target, + std::string axis, + BCAnimationCurve &curve, + BC_global_rotation_type global_rotation_type) +{ + BCFrames frames; + BCValues values; + curve.get_frames(frames); + curve.get_values(values); + std::string channel_target = curve.get_channel_target(); + + fprintf( + stdout, "Export animation curve %s (%d control points)\n", id.c_str(), int(frames.size())); + openAnimation(id, name); + BC_animation_source_type source_type = (curve.is_rotation_curve()) ? BC_SOURCE_TYPE_ANGLE : + BC_SOURCE_TYPE_VALUE; + + std::string input_id = collada_source_from_values( + BC_SOURCE_TYPE_TIMEFRAME, COLLADASW::InputSemantic::INPUT, frames, id, axis); + std::string output_id = collada_source_from_values( + source_type, COLLADASW::InputSemantic::OUTPUT, values, id, axis); + + bool has_tangents = false; + std::string interpolation_id; + if (this->export_settings.get_keep_smooth_curves()) { + interpolation_id = collada_interpolation_source(curve, id, axis, &has_tangents); + } + else { + interpolation_id = collada_linear_interpolation_source(frames.size(), id); + } + + std::string intangent_id; + std::string outtangent_id; + if (has_tangents) { + intangent_id = collada_tangent_from_curve( + COLLADASW::InputSemantic::IN_TANGENT, curve, id, axis); + outtangent_id = collada_tangent_from_curve( + COLLADASW::InputSemantic::OUT_TANGENT, curve, id, axis); + } + + std::string sampler_id = std::string(id) + SAMPLER_ID_SUFFIX; + + COLLADASW::LibraryAnimations::Sampler sampler(sw, sampler_id); + + sampler.addInput(COLLADASW::InputSemantic::INPUT, COLLADABU::URI(EMPTY_STRING, input_id)); + sampler.addInput(COLLADASW::InputSemantic::OUTPUT, COLLADABU::URI(EMPTY_STRING, output_id)); + sampler.addInput(COLLADASW::InputSemantic::INTERPOLATION, + COLLADABU::URI(EMPTY_STRING, interpolation_id)); + + if (has_tangents) { + sampler.addInput(COLLADASW::InputSemantic::IN_TANGENT, + COLLADABU::URI(EMPTY_STRING, intangent_id)); + sampler.addInput(COLLADASW::InputSemantic::OUT_TANGENT, + COLLADABU::URI(EMPTY_STRING, outtangent_id)); + } + + addSampler(sampler); + addChannel(COLLADABU::URI(EMPTY_STRING, sampler_id), collada_target); + + closeAnimation(); +} + +void AnimationExporter::export_collada_matrix_animation( + std::string id, + std::string name, + std::string target, + BCFrames &frames, + BCMatrixSampleMap &samples, + BC_global_rotation_type global_rotation_type, + Matrix &parentinv) +{ + fprintf( + stdout, "Export animation matrix %s (%d control points)\n", id.c_str(), int(frames.size())); + + openAnimationWithClip(id, name); + + std::string input_id = collada_source_from_values( + BC_SOURCE_TYPE_TIMEFRAME, COLLADASW::InputSemantic::INPUT, frames, id, ""); + std::string output_id = collada_source_from_values(samples, id, global_rotation_type, parentinv); + std::string interpolation_id = collada_linear_interpolation_source(frames.size(), id); + + std::string sampler_id = std::string(id) + SAMPLER_ID_SUFFIX; + COLLADASW::LibraryAnimations::Sampler sampler(sw, sampler_id); + + sampler.addInput(COLLADASW::InputSemantic::INPUT, COLLADABU::URI(EMPTY_STRING, input_id)); + sampler.addInput(COLLADASW::InputSemantic::OUTPUT, COLLADABU::URI(EMPTY_STRING, output_id)); + sampler.addInput(COLLADASW::InputSemantic::INTERPOLATION, + COLLADABU::URI(EMPTY_STRING, interpolation_id)); + + /* Matrix animation has no tangents */ + + addSampler(sampler); + addChannel(COLLADABU::URI(EMPTY_STRING, sampler_id), target); + + closeAnimation(); +} + +std::string AnimationExporter::get_semantic_suffix(COLLADASW::InputSemantic::Semantics semantic) +{ + switch (semantic) { + case COLLADASW::InputSemantic::INPUT: + return INPUT_SOURCE_ID_SUFFIX; + case COLLADASW::InputSemantic::OUTPUT: + return OUTPUT_SOURCE_ID_SUFFIX; + case COLLADASW::InputSemantic::INTERPOLATION: + return INTERPOLATION_SOURCE_ID_SUFFIX; + case COLLADASW::InputSemantic::IN_TANGENT: + return INTANGENT_SOURCE_ID_SUFFIX; + case COLLADASW::InputSemantic::OUT_TANGENT: + return OUTTANGENT_SOURCE_ID_SUFFIX; + default: + break; + } + return ""; +} + +void AnimationExporter::add_source_parameters(COLLADASW::SourceBase::ParameterNameList ¶m, + COLLADASW::InputSemantic::Semantics semantic, + bool is_rot, + const std::string axis, + bool transform) +{ + switch (semantic) { + case COLLADASW::InputSemantic::INPUT: + param.push_back("TIME"); + break; + case COLLADASW::InputSemantic::OUTPUT: + if (is_rot) { + param.push_back("ANGLE"); + } + else { + if (axis != "") { + param.push_back(axis); + } + else if (transform) { + param.push_back("TRANSFORM"); + } + else { + /* assumes if axis isn't specified all axises are added */ + param.push_back("X"); + param.push_back("Y"); + param.push_back("Z"); + } + } + break; + case COLLADASW::InputSemantic::IN_TANGENT: + case COLLADASW::InputSemantic::OUT_TANGENT: + param.push_back("X"); + param.push_back("Y"); + break; + default: + break; + } +} + +std::string AnimationExporter::collada_tangent_from_curve( + COLLADASW::InputSemantic::Semantics semantic, + BCAnimationCurve &curve, + const std::string &anim_id, + std::string axis_name) +{ + Scene *scene = this->export_settings.get_scene(); + + std::string channel = curve.get_channel_target(); + + const std::string source_id = anim_id + get_semantic_suffix(semantic); + + bool is_angle = (bc_startswith(channel, "rotation") || channel == "spot_size"); + + COLLADASW::FloatSourceF source(mSW); + source.setId(source_id); + source.setArrayId(source_id + ARRAY_ID_SUFFIX); + source.setAccessorCount(curve.sample_count()); + source.setAccessorStride(2); + + COLLADASW::SourceBase::ParameterNameList ¶m = source.getParameterNameList(); + add_source_parameters(param, semantic, is_angle, axis_name, false); + + source.prepareToAppendValues(); + + const FCurve *fcu = curve.get_fcurve(); + int tangent = (semantic == COLLADASW::InputSemantic::IN_TANGENT) ? 0 : 2; + + for (int i = 0; i < fcu->totvert; i++) { + BezTriple &bezt = fcu->bezt[i]; + + float sampled_time = bezt.vec[tangent][0]; + float sampled_val = bezt.vec[tangent][1]; + + if (is_angle) { + sampled_val = RAD2DEGF(sampled_val); + } + + source.appendValues(FRA2TIME(sampled_time)); + source.appendValues(sampled_val); + } + source.finish(); + return source_id; +} + +std::string AnimationExporter::collada_source_from_values( + BC_animation_source_type source_type, + COLLADASW::InputSemantic::Semantics semantic, + std::vector<float> &values, + const std::string &anim_id, + const std::string axis_name) +{ + BlenderContext &blender_context = this->export_settings.get_blender_context(); + Scene *scene = blender_context.get_scene(); + /* T can be float, int or double */ + + int stride = 1; + int entry_count = values.size() / stride; + std::string source_id = anim_id + get_semantic_suffix(semantic); + + COLLADASW::FloatSourceF source(mSW); + source.setId(source_id); + source.setArrayId(source_id + ARRAY_ID_SUFFIX); + source.setAccessorCount(entry_count); + source.setAccessorStride(stride); + + COLLADASW::SourceBase::ParameterNameList ¶m = source.getParameterNameList(); + add_source_parameters(param, semantic, source_type == BC_SOURCE_TYPE_ANGLE, axis_name, false); + + source.prepareToAppendValues(); + + for (int i = 0; i < entry_count; i++) { + float val = values[i]; + switch (source_type) { + case BC_SOURCE_TYPE_TIMEFRAME: + val = FRA2TIME(val); + break; + case BC_SOURCE_TYPE_ANGLE: + val = RAD2DEGF(val); + break; + default: + break; + } + source.appendValues(val); + } + + source.finish(); + + return source_id; +} + +/* + * Create a collada matrix source for a set of samples + */ +std::string AnimationExporter::collada_source_from_values( + BCMatrixSampleMap &samples, + const std::string &anim_id, + BC_global_rotation_type global_rotation_type, + Matrix &parentinv) +{ + COLLADASW::InputSemantic::Semantics semantic = COLLADASW::InputSemantic::OUTPUT; + std::string source_id = anim_id + get_semantic_suffix(semantic); + + COLLADASW::Float4x4Source source(mSW); + source.setId(source_id); + source.setArrayId(source_id + ARRAY_ID_SUFFIX); + source.setAccessorCount(samples.size()); + source.setAccessorStride(16); + + COLLADASW::SourceBase::ParameterNameList ¶m = source.getParameterNameList(); + add_source_parameters(param, semantic, false, "", true); + + source.prepareToAppendValues(); + + BCMatrixSampleMap::iterator it; + /* could be made configurable */ + int precision = (this->export_settings.get_limit_precision()) ? 6 : -1; + for (it = samples.begin(); it != samples.end(); it++) { + BCMatrix sample = BCMatrix(*it->second); + BCMatrix global_transform = this->export_settings.get_global_transform(); + DMatrix daemat; + if (this->export_settings.get_apply_global_orientation()) { + sample.apply_transform(global_transform); + } + else { + sample.add_transform(global_transform); + } + sample.get_matrix(daemat, true, precision); + source.appendValues(daemat); + } + + source.finish(); + return source_id; +} + +std::string AnimationExporter::collada_interpolation_source(const BCAnimationCurve &curve, + const std::string &anim_id, + const std::string axis, + bool *has_tangents) +{ + std::string source_id = anim_id + get_semantic_suffix(COLLADASW::InputSemantic::INTERPOLATION); + + COLLADASW::NameSource source(mSW); + source.setId(source_id); + source.setArrayId(source_id + ARRAY_ID_SUFFIX); + source.setAccessorCount(curve.sample_count()); + source.setAccessorStride(1); + + COLLADASW::SourceBase::ParameterNameList ¶m = source.getParameterNameList(); + param.push_back("INTERPOLATION"); + + source.prepareToAppendValues(); + + *has_tangents = false; + + std::vector<float> frames; + curve.get_frames(frames); + + for (unsigned int i = 0; i < curve.sample_count(); i++) { + float frame = frames[i]; + int ipo = curve.get_interpolation_type(frame); + if (ipo == BEZT_IPO_BEZ) { + source.appendValues(BEZIER_NAME); + *has_tangents = true; + } + else if (ipo == BEZT_IPO_CONST) { + source.appendValues(STEP_NAME); + } + else { + /* BEZT_IPO_LIN */ + source.appendValues(LINEAR_NAME); + } + } + /* unsupported? -- HERMITE, CARDINAL, BSPLINE, NURBS */ + + source.finish(); + + return source_id; +} + +std::string AnimationExporter::collada_linear_interpolation_source(int tot, + const std::string &anim_id) +{ + std::string source_id = anim_id + get_semantic_suffix(COLLADASW::InputSemantic::INTERPOLATION); + + COLLADASW::NameSource source(mSW); + source.setId(source_id); + source.setArrayId(source_id + ARRAY_ID_SUFFIX); + source.setAccessorCount(tot); + source.setAccessorStride(1); + + COLLADASW::SourceBase::ParameterNameList ¶m = source.getParameterNameList(); + param.push_back("INTERPOLATION"); + + source.prepareToAppendValues(); + + for (int i = 0; i < tot; i++) { + source.appendValues(LINEAR_NAME); + } + + source.finish(); + + return source_id; +} + +const std::string AnimationExporter::get_collada_name(std::string channel_type) const +{ + /* + * Translation table to map FCurve animation types to Collada animation. + * Todo: Maybe we can keep the names from the fcurves here instead of + * mapping. However this is what i found in the old code. So keep + * this map for now. + */ + static std::map<std::string, std::string> BC_CHANNEL_BLENDER_TO_COLLADA = { + {"rotation", "rotation"}, + {"rotation_euler", "rotation"}, + {"rotation_quaternion", "rotation"}, + {"scale", "scale"}, + {"location", "location"}, + + /* Materials */ + {"specular_color", "specular"}, + {"diffuse_color", "diffuse"}, + {"ior", "index_of_refraction"}, + {"specular_hardness", "specular_hardness"}, + {"alpha", "alpha"}, + + /* Lights */ + {"color", "color"}, + {"fall_off_angle", "falloff_angle"}, + {"spot_size", "falloff_angle"}, + {"fall_off_exponent", "falloff_exponent"}, + {"spot_blend", "falloff_exponent"}, + /* Special blender profile (todo: make this more elegant). */ + {"blender/blender_dist", "blender/blender_dist"}, + /* Special blender profile (todo: make this more elegant). */ + {"distance", "blender/blender_dist"}, + + /* Cameras */ + {"lens", "xfov"}, + {"xfov", "xfov"}, + {"xmag", "xmag"}, + {"zfar", "zfar"}, + {"znear", "znear"}, + {"ortho_scale", "xmag"}, + {"clip_end", "zfar"}, + {"clip_start", "znear"}}; + + std::map<std::string, std::string>::iterator name_it = BC_CHANNEL_BLENDER_TO_COLLADA.find( + channel_type); + if (name_it == BC_CHANNEL_BLENDER_TO_COLLADA.end()) { + return ""; + } + std::string tm_name = name_it->second; + return tm_name; +} + +/* + * Assign sid of the animated parameter or transform for rotation, + * axis name is always appended and the value of append_axis is ignored + */ +std::string AnimationExporter::get_collada_sid(const BCAnimationCurve &curve, + const std::string axis_name) +{ + std::string channel_target = curve.get_channel_target(); + std::string channel_type = curve.get_channel_type(); + std::string tm_name = get_collada_name(channel_type); + + bool is_angle = curve.is_rotation_curve(); + + if (tm_name.size()) { + if (is_angle) { + return tm_name + std::string(axis_name) + ".ANGLE"; + } + else if (axis_name != "") { + return tm_name + "." + std::string(axis_name); + } + else { + return tm_name; + } + } + + return tm_name; +} + +#ifdef WITH_MORPH_ANIMATION +/* TODO: This function needs to be implemented similar to the material animation export + * So we have to update BCSample for this to work. */ +void AnimationExporter::export_morph_animation(Object *ob, BCAnimationSampler &sampler) +{ + FCurve *fcu; + Key *key = BKE_key_from_object(ob); + if (!key) { + return; + } + + if (key->adt && key->adt->action) { + fcu = (FCurve *)key->adt->action->curves.first; + + while (fcu) { + BC_animation_transform_type tm_type = get_transform_type(fcu->rna_path); + + create_keyframed_animation(ob, fcu, tm_type, true, sampler); + + fcu = fcu->next; + } + } +} +#endif diff --git a/source/blender/io/collada/AnimationExporter.h b/source/blender/io/collada/AnimationExporter.h new file mode 100644 index 00000000000..64751ec5327 --- /dev/null +++ b/source/blender/io/collada/AnimationExporter.h @@ -0,0 +1,266 @@ +/* + * 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. + */ + +#ifndef __ANIMATIONEXPORTER_H__ +#define __ANIMATIONEXPORTER_H__ + +#include <stdlib.h> +#include <stdio.h> +#include <math.h> + +#include "BCAnimationCurve.h" + +extern "C" { +#include "DNA_scene_types.h" +#include "DNA_object_types.h" +#include "DNA_anim_types.h" +#include "DNA_action_types.h" +#include "DNA_curve_types.h" +#include "DNA_light_types.h" +#include "DNA_camera_types.h" +#include "DNA_armature_types.h" +#include "DNA_material_types.h" +#include "DNA_constraint_types.h" +#include "DNA_scene_types.h" + +#include "BLI_math.h" +#include "BLI_string.h" +#include "BLI_listbase.h" +#include "BLI_utildefines.h" + +#include "BKE_fcurve.h" +#include "BKE_animsys.h" +#include "BKE_scene.h" +#include "BKE_action.h" // pose functions +#include "BKE_armature.h" +#include "BKE_object.h" +#include "BKE_constraint.h" +#include "BIK_api.h" +#include "ED_object.h" +} + +#include "MEM_guardedalloc.h" + +#include "RNA_access.h" + +#include "COLLADASWSource.h" +#include "COLLADASWInstanceGeometry.h" +#include "COLLADASWInputList.h" +#include "COLLADASWPrimitves.h" +#include "COLLADASWVertices.h" +#include "COLLADASWLibraryAnimations.h" +#include "COLLADASWParamTemplate.h" +#include "COLLADASWParamBase.h" +#include "COLLADASWSampler.h" +#include "COLLADASWConstants.h" +#include "COLLADASWBaseInputElement.h" + +#include "EffectExporter.h" +#include "BCAnimationSampler.h" +#include "collada_internal.h" + +#include "IK_solver.h" + +#include <vector> +#include <map> +#include <algorithm> // std::find + +typedef enum BC_animation_source_type { + BC_SOURCE_TYPE_VALUE, + BC_SOURCE_TYPE_ANGLE, + BC_SOURCE_TYPE_TIMEFRAME, +} BC_animation_source_type; + +typedef enum BC_global_rotation_type { + BC_NO_ROTATION, + BC_OBJECT_ROTATION, + BC_DATA_ROTATION +} BC_global_rotation_type; + +class AnimationExporter : COLLADASW::LibraryAnimations { + private: + COLLADASW::StreamWriter *sw; + BCExportSettings &export_settings; + + BC_global_rotation_type get_global_rotation_type(Object *ob); + + public: + AnimationExporter(COLLADASW::StreamWriter *sw, BCExportSettings &export_settings) + : COLLADASW::LibraryAnimations(sw), sw(sw), export_settings(export_settings) + { + } + + bool exportAnimations(); + + // called for each exported object + void operator()(Object *ob); + + protected: + void export_object_constraint_animation(Object *ob); + + void export_morph_animation(Object *ob); + + void write_bone_animation_matrix(Object *ob_arm, Bone *bone); + + void write_bone_animation(Object *ob_arm, Bone *bone); + + void sample_and_write_bone_animation(Object *ob_arm, Bone *bone, int transform_type); + + void sample_and_write_bone_animation_matrix(Object *ob_arm, Bone *bone); + + void sample_animation(float *v, + std::vector<float> &frames, + int type, + Bone *bone, + Object *ob_arm, + bPoseChannel *pChan); + + void sample_animation(std::vector<float[4][4]> &mats, + std::vector<float> &frames, + Bone *bone, + Object *ob_arm, + bPoseChannel *pChan); + + // dae_bone_animation -> add_bone_animation + // (blend this into dae_bone_animation) + void dae_bone_animation(std::vector<float> &fra, + float *v, + int tm_type, + int axis, + std::string ob_name, + std::string bone_name); + + void dae_baked_animation(std::vector<float> &fra, Object *ob_arm, Bone *bone); + + void dae_baked_object_animation(std::vector<float> &fra, Object *ob); + + float convert_time(float frame); + + float convert_angle(float angle); + + std::vector<std::vector<std::string>> anim_meta; + + /* Main entry point into Animation export (called for each exported object) */ + void exportAnimation(Object *ob, BCAnimationSampler &sampler); + + /* export animation as separate trans/rot/scale curves */ + void export_curve_animation_set(Object *ob, BCAnimationSampler &sampler, bool export_tm_curves); + + /* export one single curve */ + void export_curve_animation(Object *ob, BCAnimationCurve &curve); + + /* export animation as matrix data */ + void export_matrix_animation(Object *ob, BCAnimationSampler &sampler); + + /* step through the bone hierarchy */ + void export_bone_animations_recursive(Object *ob_arm, Bone *bone, BCAnimationSampler &sampler); + + /* Export for one bone */ + void export_bone_animation(Object *ob, Bone *bone, BCFrames &frames, BCMatrixSampleMap &outmats); + + /* call to the low level collada exporter */ + void export_collada_curve_animation(std::string id, + std::string name, + std::string target, + std::string axis, + BCAnimationCurve &curve, + BC_global_rotation_type global_rotation_type); + + /* call to the low level collada exporter */ + void export_collada_matrix_animation(std::string id, + std::string name, + std::string target, + BCFrames &frames, + BCMatrixSampleMap &outmats, + BC_global_rotation_type global_rotation_type, + Matrix &parentinv); + + BCAnimationCurve *get_modified_export_curve(Object *ob, + BCAnimationCurve &curve, + BCAnimationCurveMap &curves); + + /* Helper functions */ + void openAnimationWithClip(std::string id, std::string name); + bool open_animation_container(bool has_container, Object *ob); + void close_animation_container(bool has_container); + + /* Input and Output sources (single valued) */ + std::string collada_source_from_values(BC_animation_source_type tm_channel, + COLLADASW::InputSemantic::Semantics semantic, + std::vector<float> &values, + const std::string &anim_id, + const std::string axis_name); + + /* Output sources (matrix data) */ + std::string collada_source_from_values(BCMatrixSampleMap &samples, + const std::string &anim_id, + BC_global_rotation_type global_rotation_type, + Matrix &parentinv); + + /* Interpolation sources */ + std::string collada_linear_interpolation_source(int tot, const std::string &anim_id); + + /* source ID = animation_name + semantic_suffix */ + + std::string get_semantic_suffix(COLLADASW::InputSemantic::Semantics semantic); + + void add_source_parameters(COLLADASW::SourceBase::ParameterNameList ¶m, + COLLADASW::InputSemantic::Semantics semantic, + bool is_rot, + const std::string axis, + bool transform); + + int get_point_in_curve(BCBezTriple &bezt, + COLLADASW::InputSemantic::Semantics semantic, + bool is_angle, + float *values); + int get_point_in_curve(const BCAnimationCurve &curve, + float sample_frame, + COLLADASW::InputSemantic::Semantics semantic, + bool is_angle, + float *values); + + std::string collada_tangent_from_curve(COLLADASW::InputSemantic::Semantics semantic, + BCAnimationCurve &curve, + const std::string &anim_id, + const std::string axis_name); + + std::string collada_interpolation_source(const BCAnimationCurve &curve, + const std::string &anim_id, + std::string axis_name, + bool *has_tangents); + + std::string get_axis_name(std::string channel, int id); + const std::string get_collada_name(std::string channel_target) const; + std::string get_collada_sid(const BCAnimationCurve &curve, const std::string axis_name); + + /* ===================================== */ + /* Currently unused or not (yet?) needed */ + /* ===================================== */ + + bool is_bone_deform_group(Bone *bone); + +#if 0 + BC_animation_transform_type _get_transform_type(const std::string path); + void get_eul_source_for_quat(std::vector<float> &cache, Object *ob); +#endif + +#ifdef WITH_MORPH_ANIMATION + void export_morph_animation(Object *ob, BCAnimationSampler &sampler); +#endif +}; + +#endif /* __ANIMATIONEXPORTER_H__ */ diff --git a/source/blender/io/collada/AnimationImporter.cpp b/source/blender/io/collada/AnimationImporter.cpp new file mode 100644 index 00000000000..715cd9e1a12 --- /dev/null +++ b/source/blender/io/collada/AnimationImporter.cpp @@ -0,0 +1,2232 @@ +/* + * 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 collada + */ + +#include <stddef.h> + +/* COLLADABU_ASSERT, may be able to remove later */ +#include "COLLADABUPlatform.h" + +#include "DNA_armature_types.h" + +#include "ED_keyframing.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_string.h" +#include "BLI_string_utils.h" + +#include "BLT_translation.h" + +#include "BKE_action.h" +#include "BKE_armature.h" +#include "BKE_fcurve.h" +#include "BKE_object.h" + +#include "MEM_guardedalloc.h" + +#include "collada_utils.h" +#include "AnimationImporter.h" +#include "ArmatureImporter.h" +#include "MaterialExporter.h" + +#include <algorithm> + +/* first try node name, if not available (since is optional), fall back to original id */ +template<class T> static const char *bc_get_joint_name(T *node) +{ + const std::string &id = node->getName(); + return id.size() ? id.c_str() : node->getOriginalId().c_str(); +} + +FCurve *AnimationImporter::create_fcurve(int array_index, const char *rna_path) +{ + FCurve *fcu = (FCurve *)MEM_callocN(sizeof(FCurve), "FCurve"); + fcu->flag = (FCURVE_VISIBLE | FCURVE_AUTO_HANDLES | FCURVE_SELECTED); + fcu->rna_path = BLI_strdupn(rna_path, strlen(rna_path)); + fcu->array_index = array_index; + return fcu; +} + +void AnimationImporter::add_bezt(FCurve *fcu, + float frame, + float value, + eBezTriple_Interpolation ipo) +{ + // float fps = (float)FPS; + BezTriple bez; + memset(&bez, 0, sizeof(BezTriple)); + bez.vec[1][0] = frame; + bez.vec[1][1] = value; + bez.ipo = ipo; /* use default interpolation mode here... */ + bez.f1 = bez.f2 = bez.f3 = SELECT; + bez.h1 = bez.h2 = HD_AUTO; + insert_bezt_fcurve(fcu, &bez, INSERTKEY_NOFLAGS); + calchandles_fcurve(fcu); +} + +/* create one or several fcurves depending on the number of parameters being animated */ +void AnimationImporter::animation_to_fcurves(COLLADAFW::AnimationCurve *curve) +{ + COLLADAFW::FloatOrDoubleArray &input = curve->getInputValues(); + COLLADAFW::FloatOrDoubleArray &output = curve->getOutputValues(); + + float fps = (float)FPS; + size_t dim = curve->getOutDimension(); + unsigned int i; + + std::vector<FCurve *> &fcurves = curve_map[curve->getUniqueId()]; + + switch (dim) { + case 1: /* X, Y, Z or angle */ + case 3: /* XYZ */ + case 4: + case 16: /* matrix */ + { + for (i = 0; i < dim; i++) { + FCurve *fcu = (FCurve *)MEM_callocN(sizeof(FCurve), "FCurve"); + + fcu->flag = (FCURVE_VISIBLE | FCURVE_AUTO_HANDLES | FCURVE_SELECTED); + fcu->array_index = 0; + fcu->auto_smoothing = U.auto_smoothing_new; + + for (unsigned int j = 0; j < curve->getKeyCount(); j++) { + BezTriple bez; + memset(&bez, 0, sizeof(BezTriple)); + + /* input, output */ + bez.vec[1][0] = bc_get_float_value(input, j) * fps; + bez.vec[1][1] = bc_get_float_value(output, j * dim + i); + bez.h1 = bez.h2 = HD_AUTO; + + if (curve->getInterpolationType() == COLLADAFW::AnimationCurve::INTERPOLATION_BEZIER || + curve->getInterpolationType() == COLLADAFW::AnimationCurve::INTERPOLATION_STEP) { + COLLADAFW::FloatOrDoubleArray &intan = curve->getInTangentValues(); + COLLADAFW::FloatOrDoubleArray &outtan = curve->getOutTangentValues(); + + /* intangent */ + unsigned int index = 2 * (j * dim + i); + bez.vec[0][0] = bc_get_float_value(intan, index) * fps; + bez.vec[0][1] = bc_get_float_value(intan, index + 1); + + /* outtangent */ + bez.vec[2][0] = bc_get_float_value(outtan, index) * fps; + bez.vec[2][1] = bc_get_float_value(outtan, index + 1); + if (curve->getInterpolationType() == COLLADAFW::AnimationCurve::INTERPOLATION_BEZIER) { + bez.ipo = BEZT_IPO_BEZ; + bez.h1 = bez.h2 = HD_AUTO_ANIM; + } + else { + bez.ipo = BEZT_IPO_CONST; + } + } + else { + bez.ipo = BEZT_IPO_LIN; + } +#if 0 + bez.ipo = U.ipo_new; /* use default interpolation mode here... */ +#endif + bez.f1 = bez.f2 = bez.f3 = SELECT; + + insert_bezt_fcurve(fcu, &bez, INSERTKEY_NOFLAGS); + } + + calchandles_fcurve(fcu); + + fcurves.push_back(fcu); + unused_curves.push_back(fcu); + } + } break; + default: + fprintf(stderr, + "Output dimension of %d is not yet supported (animation id = %s)\n", + (int)dim, + curve->getOriginalId().c_str()); + } +} + +void AnimationImporter::fcurve_deg_to_rad(FCurve *cu) +{ + for (unsigned int i = 0; i < cu->totvert; i++) { + /* TODO convert handles too */ + cu->bezt[i].vec[1][1] *= DEG2RADF(1.0f); + cu->bezt[i].vec[0][1] *= DEG2RADF(1.0f); + cu->bezt[i].vec[2][1] *= DEG2RADF(1.0f); + } +} + +void AnimationImporter::fcurve_scale(FCurve *cu, int scale) +{ + for (unsigned int i = 0; i < cu->totvert; i++) { + /* TODO convert handles too */ + cu->bezt[i].vec[1][1] *= scale; + cu->bezt[i].vec[0][1] *= scale; + cu->bezt[i].vec[2][1] *= scale; + } +} + +void AnimationImporter::fcurve_is_used(FCurve *fcu) +{ + unused_curves.erase(std::remove(unused_curves.begin(), unused_curves.end(), fcu), + unused_curves.end()); +} + +void AnimationImporter::add_fcurves_to_object(Main *bmain, + Object *ob, + std::vector<FCurve *> &curves, + char *rna_path, + int array_index, + Animation *animated) +{ + bAction *act; + + if (!ob->adt || !ob->adt->action) { + act = ED_id_action_ensure(bmain, (ID *)&ob->id); + } + else { + act = ob->adt->action; + } + + std::vector<FCurve *>::iterator it; + int i; + +#if 0 + char *p = strstr(rna_path, "rotation_euler"); + bool is_rotation = p && *(p + strlen("rotation_euler")) == '\0'; + + /* convert degrees to radians for rotation */ + if (is_rotation) { + fcurve_deg_to_rad(fcu); + } +#endif + + for (it = curves.begin(), i = 0; it != curves.end(); it++, i++) { + FCurve *fcu = *it; + fcu->rna_path = BLI_strdupn(rna_path, strlen(rna_path)); + + if (array_index == -1) { + fcu->array_index = i; + } + else { + fcu->array_index = array_index; + } + + if (ob->type == OB_ARMATURE) { + bActionGroup *grp = NULL; + const char *bone_name = bc_get_joint_name(animated->node); + + if (bone_name) { + /* try to find group */ + grp = BKE_action_group_find_name(act, bone_name); + + /* no matching groups, so add one */ + if (grp == NULL) { + /* Add a new group, and make it active */ + grp = (bActionGroup *)MEM_callocN(sizeof(bActionGroup), "bActionGroup"); + + grp->flag = AGRP_SELECTED; + BLI_strncpy(grp->name, bone_name, sizeof(grp->name)); + + BLI_addtail(&act->groups, grp); + BLI_uniquename(&act->groups, + grp, + CTX_DATA_(BLT_I18NCONTEXT_ID_ACTION, "Group"), + '.', + offsetof(bActionGroup, name), + 64); + } + + /* add F-Curve to group */ + action_groups_add_channel(act, grp, fcu); + fcurve_is_used(fcu); + } +#if 0 + if (is_rotation) { + fcurves_actionGroup_map[grp].push_back(fcu); + } +#endif + } + else { + BLI_addtail(&act->curves, fcu); + fcurve_is_used(fcu); + } + } +} + +AnimationImporter::~AnimationImporter() +{ + /* free unused FCurves */ + for (std::vector<FCurve *>::iterator it = unused_curves.begin(); it != unused_curves.end(); + it++) { + free_fcurve(*it); + } + + if (unused_curves.size()) { + fprintf(stderr, "removed %d unused curves\n", (int)unused_curves.size()); + } +} + +bool AnimationImporter::write_animation(const COLLADAFW::Animation *anim) +{ + if (anim->getAnimationType() == COLLADAFW::Animation::ANIMATION_CURVE) { + COLLADAFW::AnimationCurve *curve = (COLLADAFW::AnimationCurve *)anim; + + /* XXX Don't know if it's necessary + * Should we check outPhysicalDimension? */ + if (curve->getInPhysicalDimension() != COLLADAFW::PHYSICAL_DIMENSION_TIME) { + fprintf(stderr, "Inputs physical dimension is not time.\n"); + return true; + } + + /* a curve can have mixed interpolation type, + * in this case curve->getInterpolationTypes returns a list of interpolation types per key */ + COLLADAFW::AnimationCurve::InterpolationType interp = curve->getInterpolationType(); + + if (interp != COLLADAFW::AnimationCurve::INTERPOLATION_MIXED) { + switch (interp) { + case COLLADAFW::AnimationCurve::INTERPOLATION_LINEAR: + case COLLADAFW::AnimationCurve::INTERPOLATION_BEZIER: + case COLLADAFW::AnimationCurve::INTERPOLATION_STEP: + animation_to_fcurves(curve); + break; + default: + /* TODO there're also CARDINAL, HERMITE, BSPLINE and STEP types */ + fprintf(stderr, + "CARDINAL, HERMITE and BSPLINE anim interpolation types not supported yet.\n"); + break; + } + } + else { + /* not supported yet */ + fprintf(stderr, "MIXED anim interpolation type is not supported yet.\n"); + } + } + else { + fprintf(stderr, "FORMULA animation type is not supported yet.\n"); + } + + return true; +} + +/* called on post-process stage after writeVisualScenes */ +bool AnimationImporter::write_animation_list(const COLLADAFW::AnimationList *animlist) +{ + const COLLADAFW::UniqueId &animlist_id = animlist->getUniqueId(); + animlist_map[animlist_id] = animlist; + +#if 0 + + /* should not happen */ + if (uid_animated_map.find(animlist_id) == uid_animated_map.end()) { + return true; + } + + /* for bones rna_path is like: pose.bones["bone-name"].rotation */ + +#endif + + return true; +} + +/* \todo refactor read_node_transform to not automatically apply anything, + * but rather return the transform matrix, so caller can do with it what is + * necessary. Same for \ref get_node_mat */ +void AnimationImporter::read_node_transform(COLLADAFW::Node *node, Object *ob) +{ + float mat[4][4]; + TransformReader::get_node_mat(mat, node, &uid_animated_map, ob); + if (ob) { + copy_m4_m4(ob->obmat, mat); + BKE_object_apply_mat4(ob, ob->obmat, 0, 0); + } +} + +#if 0 +virtual void AnimationImporter::change_eul_to_quat(Object *ob, bAction *act) +{ + bActionGroup *grp; + int i; + + for (grp = (bActionGroup *)act->groups.first; grp; grp = grp->next) { + + FCurve *eulcu[3] = {NULL, NULL, NULL}; + + if (fcurves_actionGroup_map.find(grp) == fcurves_actionGroup_map.end()) { + continue; + } + + std::vector<FCurve *> &rot_fcurves = fcurves_actionGroup_map[grp]; + + if (rot_fcurves.size() > 3) { + continue; + } + + for (i = 0; i < rot_fcurves.size(); i++) { + eulcu[rot_fcurves[i]->array_index] = rot_fcurves[i]; + } + + char joint_path[100]; + char rna_path[100]; + + BLI_snprintf(joint_path, sizeof(joint_path), "pose.bones[\"%s\"]", grp->name); + BLI_snprintf(rna_path, sizeof(rna_path), "%s.rotation_quaternion", joint_path); + + FCurve *quatcu[4] = { + create_fcurve(0, rna_path), + create_fcurve(1, rna_path), + create_fcurve(2, rna_path), + create_fcurve(3, rna_path), + }; + + bPoseChannel *chan = BKE_pose_channel_find_name(ob->pose, grp->name); + + float m4[4][4], irest[3][3]; + invert_m4_m4(m4, chan->bone->arm_mat); + copy_m3_m4(irest, m4); + + for (i = 0; i < 3; i++) { + + FCurve *cu = eulcu[i]; + + if (!cu) { + continue; + } + + for (int j = 0; j < cu->totvert; j++) { + float frame = cu->bezt[j].vec[1][0]; + + float eul[3] = { + eulcu[0] ? evaluate_fcurve(eulcu[0], frame) : 0.0f, + eulcu[1] ? evaluate_fcurve(eulcu[1], frame) : 0.0f, + eulcu[2] ? evaluate_fcurve(eulcu[2], frame) : 0.0f, + }; + + /* make eul relative to bone rest pose */ + float rot[3][3], rel[3][3], quat[4]; + +# if 0 + eul_to_mat3(rot, eul); + mul_m3_m3m3(rel, irest, rot); + mat3_to_quat(quat, rel); +# endif + + eul_to_quat(quat, eul); + + for (int k = 0; k < 4; k++) { + create_bezt(quatcu[k], frame, quat[k], U.ipo_new); + } + } + } + + /* now replace old Euler curves */ + + for (i = 0; i < 3; i++) { + if (!eulcu[i]) { + continue; + } + + action_groups_remove_channel(act, eulcu[i]); + free_fcurve(eulcu[i]); + } + + chan->rotmode = ROT_MODE_QUAT; + + for (i = 0; i < 4; i++) { + action_groups_add_channel(act, grp, quatcu[i]); + } + } + + bPoseChannel *pchan; + for (pchan = (bPoseChannel *)ob->pose->chanbase.first; pchan; pchan = pchan->next) { + pchan->rotmode = ROT_MODE_QUAT; + } +} +#endif + +/* sets the rna_path and array index to curve */ +void AnimationImporter::modify_fcurve(std::vector<FCurve *> *curves, + const char *rna_path, + int array_index, + int scale) +{ + std::vector<FCurve *>::iterator it; + int i; + for (it = curves->begin(), i = 0; it != curves->end(); it++, i++) { + FCurve *fcu = *it; + fcu->rna_path = BLI_strdup(rna_path); + + if (array_index == -1) { + fcu->array_index = i; + } + else { + fcu->array_index = array_index; + } + + if (scale != 1) { + fcurve_scale(fcu, scale); + } + + fcurve_is_used(fcu); + } +} + +void AnimationImporter::unused_fcurve(std::vector<FCurve *> *curves) +{ + /* when an error happens and we can't actually use curve remove it from unused_curves */ + std::vector<FCurve *>::iterator it; + for (it = curves->begin(); it != curves->end(); it++) { + FCurve *fcu = *it; + fcurve_is_used(fcu); + } +} + +void AnimationImporter::find_frames(std::vector<float> *frames, std::vector<FCurve *> *curves) +{ + std::vector<FCurve *>::iterator iter; + for (iter = curves->begin(); iter != curves->end(); iter++) { + FCurve *fcu = *iter; + + for (unsigned int k = 0; k < fcu->totvert; k++) { + /* get frame value from bezTriple */ + float fra = fcu->bezt[k].vec[1][0]; + /* if frame already not added add frame to frames */ + if (std::find(frames->begin(), frames->end(), fra) == frames->end()) { + frames->push_back(fra); + } + } + } +} + +static int get_animation_axis_index(const COLLADABU::Math::Vector3 &axis) +{ + int index; + if (COLLADABU::Math::Vector3::UNIT_X == axis) { + index = 0; + } + else if (COLLADABU::Math::Vector3::UNIT_Y == axis) { + index = 1; + } + else if (COLLADABU::Math::Vector3::UNIT_Z == axis) { + index = 2; + } + else { + index = -1; + } + return index; +} + +/* creates the rna_paths and array indices of fcurves from animations using transformation and + * bound animation class of each animation. */ +void AnimationImporter::Assign_transform_animations( + COLLADAFW::Transformation *transform, + const COLLADAFW::AnimationList::AnimationBinding *binding, + std::vector<FCurve *> *curves, + bool is_joint, + char *joint_path) +{ + COLLADAFW::Transformation::TransformationType tm_type = transform->getTransformationType(); + bool is_matrix = tm_type == COLLADAFW::Transformation::MATRIX; + bool is_rotation = tm_type == COLLADAFW::Transformation::ROTATE; + + /* to check if the no of curves are valid */ + bool xyz = ((tm_type == COLLADAFW::Transformation::TRANSLATE || + tm_type == COLLADAFW::Transformation::SCALE) && + binding->animationClass == COLLADAFW::AnimationList::POSITION_XYZ); + + if (!((!xyz && curves->size() == 1) || (xyz && curves->size() == 3) || is_matrix)) { + fprintf(stderr, "expected %d curves, got %d\n", xyz ? 3 : 1, (int)curves->size()); + return; + } + + char rna_path[100]; + + switch (tm_type) { + case COLLADAFW::Transformation::TRANSLATE: + case COLLADAFW::Transformation::SCALE: { + bool loc = tm_type == COLLADAFW::Transformation::TRANSLATE; + if (is_joint) { + BLI_snprintf(rna_path, sizeof(rna_path), "%s.%s", joint_path, loc ? "location" : "scale"); + } + else { + BLI_strncpy(rna_path, loc ? "location" : "scale", sizeof(rna_path)); + } + + switch (binding->animationClass) { + case COLLADAFW::AnimationList::POSITION_X: + modify_fcurve(curves, rna_path, 0); + break; + case COLLADAFW::AnimationList::POSITION_Y: + modify_fcurve(curves, rna_path, 1); + break; + case COLLADAFW::AnimationList::POSITION_Z: + modify_fcurve(curves, rna_path, 2); + break; + case COLLADAFW::AnimationList::POSITION_XYZ: + modify_fcurve(curves, rna_path, -1); + break; + default: + unused_fcurve(curves); + fprintf(stderr, + "AnimationClass %d is not supported for %s.\n", + binding->animationClass, + loc ? "TRANSLATE" : "SCALE"); + } + break; + } + + case COLLADAFW::Transformation::ROTATE: { + if (is_joint) { + BLI_snprintf(rna_path, sizeof(rna_path), "%s.rotation_euler", joint_path); + } + else { + BLI_strncpy(rna_path, "rotation_euler", sizeof(rna_path)); + } + std::vector<FCurve *>::iterator iter; + for (iter = curves->begin(); iter != curves->end(); iter++) { + FCurve *fcu = *iter; + + /* if transform is rotation the fcurves values must be turned in to radian. */ + if (is_rotation) { + fcurve_deg_to_rad(fcu); + } + } + COLLADAFW::Rotate *rot = (COLLADAFW::Rotate *)transform; + COLLADABU::Math::Vector3 &axis = rot->getRotationAxis(); + + switch (binding->animationClass) { + case COLLADAFW::AnimationList::ANGLE: { + int axis_index = get_animation_axis_index(axis); + if (axis_index >= 0) { + modify_fcurve(curves, rna_path, axis_index); + } + else { + unused_fcurve(curves); + } + } break; + case COLLADAFW::AnimationList::AXISANGLE: + /* TODO convert axis-angle to quat? or XYZ? */ + default: + unused_fcurve(curves); + fprintf(stderr, + "AnimationClass %d is not supported for ROTATE transformation.\n", + binding->animationClass); + } + break; + } + + case COLLADAFW::Transformation::MATRIX: +#if 0 + { + COLLADAFW::Matrix *mat = (COLLADAFW::Matrix *)transform; + COLLADABU::Math::Matrix4 mat4 = mat->getMatrix(); + switch (binding->animationClass) { + case COLLADAFW::AnimationList::TRANSFORM: + } + } +#endif + unused_fcurve(curves); + break; + case COLLADAFW::Transformation::SKEW: + case COLLADAFW::Transformation::LOOKAT: + unused_fcurve(curves); + fprintf(stderr, "Animation of SKEW and LOOKAT transformations is not supported yet.\n"); + break; + } +} + +/* creates the rna_paths and array indices of fcurves from animations using color and bound + * animation class of each animation. */ +void AnimationImporter::Assign_color_animations(const COLLADAFW::UniqueId &listid, + ListBase *AnimCurves, + const char *anim_type) +{ + char rna_path[100]; + BLI_strncpy(rna_path, anim_type, sizeof(rna_path)); + + const COLLADAFW::AnimationList *animlist = animlist_map[listid]; + if (animlist == NULL) { + fprintf(stderr, + "Collada: No animlist found for ID: %s of type %s\n", + listid.toAscii().c_str(), + anim_type); + return; + } + + const COLLADAFW::AnimationList::AnimationBindings &bindings = animlist->getAnimationBindings(); + /* all the curves belonging to the current binding */ + std::vector<FCurve *> animcurves; + for (unsigned int j = 0; j < bindings.getCount(); j++) { + animcurves = curve_map[bindings[j].animation]; + + switch (bindings[j].animationClass) { + case COLLADAFW::AnimationList::COLOR_R: + modify_fcurve(&animcurves, rna_path, 0); + break; + case COLLADAFW::AnimationList::COLOR_G: + modify_fcurve(&animcurves, rna_path, 1); + break; + case COLLADAFW::AnimationList::COLOR_B: + modify_fcurve(&animcurves, rna_path, 2); + break; + case COLLADAFW::AnimationList::COLOR_RGB: + case COLLADAFW::AnimationList::COLOR_RGBA: /* to do-> set intensity */ + modify_fcurve(&animcurves, rna_path, -1); + break; + + default: + unused_fcurve(&animcurves); + fprintf(stderr, + "AnimationClass %d is not supported for %s.\n", + bindings[j].animationClass, + "COLOR"); + } + + std::vector<FCurve *>::iterator iter; + /* Add the curves of the current animation to the object */ + for (iter = animcurves.begin(); iter != animcurves.end(); iter++) { + FCurve *fcu = *iter; + BLI_addtail(AnimCurves, fcu); + fcurve_is_used(fcu); + } + } +} + +void AnimationImporter::Assign_float_animations(const COLLADAFW::UniqueId &listid, + ListBase *AnimCurves, + const char *anim_type) +{ + char rna_path[100]; + if (animlist_map.find(listid) == animlist_map.end()) { + return; + } + else { + /* anim_type has animations */ + const COLLADAFW::AnimationList *animlist = animlist_map[listid]; + const COLLADAFW::AnimationList::AnimationBindings &bindings = animlist->getAnimationBindings(); + /* all the curves belonging to the current binding */ + std::vector<FCurve *> animcurves; + for (unsigned int j = 0; j < bindings.getCount(); j++) { + animcurves = curve_map[bindings[j].animation]; + + BLI_strncpy(rna_path, anim_type, sizeof(rna_path)); + modify_fcurve(&animcurves, rna_path, 0); + std::vector<FCurve *>::iterator iter; + /* Add the curves of the current animation to the object */ + for (iter = animcurves.begin(); iter != animcurves.end(); iter++) { + FCurve *fcu = *iter; + /* All anim_types whose values are to be converted from Degree to Radians can be ORed here + */ + if (STREQ("spot_size", anim_type)) { + /* NOTE: Do NOT convert if imported file was made by blender <= 2.69.10 + * Reason: old blender versions stored spot_size in radians (was a bug) + */ + if (this->import_from_version == "" || + BLI_strcasecmp_natural(this->import_from_version.c_str(), "2.69.10") != -1) { + fcurve_deg_to_rad(fcu); + } + } + /** XXX What About animtype "rotation" ? */ + + BLI_addtail(AnimCurves, fcu); + fcurve_is_used(fcu); + } + } + } +} + +float AnimationImporter::convert_to_focal_length(float in_xfov, + int fov_type, + float aspect, + float sensorx) +{ + /* NOTE: Needs more testing (As we currently have no official test data for this) */ + float xfov = (fov_type == CAMERA_YFOV) ? + (2.0f * atanf(aspect * tanf(DEG2RADF(in_xfov) * 0.5f))) : + DEG2RADF(in_xfov); + return fov_to_focallength(xfov, sensorx); +} + +/* + * Lens animations must be stored in COLLADA by using FOV, + * while blender internally uses focal length. + * The imported animation curves must be converted appropriately. + */ +void AnimationImporter::Assign_lens_animations(const COLLADAFW::UniqueId &listid, + ListBase *AnimCurves, + const double aspect, + Camera *cam, + const char *anim_type, + int fov_type) +{ + char rna_path[100]; + if (animlist_map.find(listid) == animlist_map.end()) { + return; + } + else { + /* anim_type has animations */ + const COLLADAFW::AnimationList *animlist = animlist_map[listid]; + const COLLADAFW::AnimationList::AnimationBindings &bindings = animlist->getAnimationBindings(); + /* all the curves belonging to the current binding */ + std::vector<FCurve *> animcurves; + for (unsigned int j = 0; j < bindings.getCount(); j++) { + animcurves = curve_map[bindings[j].animation]; + + BLI_strncpy(rna_path, anim_type, sizeof(rna_path)); + + modify_fcurve(&animcurves, rna_path, 0); + std::vector<FCurve *>::iterator iter; + /* Add the curves of the current animation to the object */ + for (iter = animcurves.begin(); iter != animcurves.end(); iter++) { + FCurve *fcu = *iter; + + for (unsigned int i = 0; i < fcu->totvert; i++) { + fcu->bezt[i].vec[0][1] = convert_to_focal_length( + fcu->bezt[i].vec[0][1], fov_type, aspect, cam->sensor_x); + fcu->bezt[i].vec[1][1] = convert_to_focal_length( + fcu->bezt[i].vec[1][1], fov_type, aspect, cam->sensor_x); + fcu->bezt[i].vec[2][1] = convert_to_focal_length( + fcu->bezt[i].vec[2][1], fov_type, aspect, cam->sensor_x); + } + + BLI_addtail(AnimCurves, fcu); + fcurve_is_used(fcu); + } + } + } +} + +void AnimationImporter::apply_matrix_curves(Object *ob, + std::vector<FCurve *> &animcurves, + COLLADAFW::Node *root, + COLLADAFW::Node *node, + COLLADAFW::Transformation *tm) +{ + bool is_joint = node->getType() == COLLADAFW::Node::JOINT; + const char *bone_name = is_joint ? bc_get_joint_name(node) : NULL; + char joint_path[200]; + if (is_joint) { + armature_importer->get_rna_path_for_joint(node, joint_path, sizeof(joint_path)); + } + + std::vector<float> frames; + find_frames(&frames, &animcurves); + + float irest_dae[4][4]; + float rest[4][4], irest[4][4]; + + if (is_joint) { + get_joint_rest_mat(irest_dae, root, node); + invert_m4(irest_dae); + + Bone *bone = BKE_armature_find_bone_name((bArmature *)ob->data, bone_name); + if (!bone) { + fprintf(stderr, "cannot find bone \"%s\"\n", bone_name); + return; + } + + unit_m4(rest); + copy_m4_m4(rest, bone->arm_mat); + invert_m4_m4(irest, rest); + } + /* new curves to assign matrix transform animation */ + FCurve *newcu[10]; /* if tm_type is matrix, then create 10 curves: 4 rot, 3 loc, 3 scale */ + unsigned int totcu = 10; + const char *tm_str = NULL; + char rna_path[200]; + for (int i = 0; i < totcu; i++) { + + int axis = i; + + if (i < 4) { + tm_str = "rotation_quaternion"; + axis = i; + } + else if (i < 7) { + tm_str = "location"; + axis = i - 4; + } + else { + tm_str = "scale"; + axis = i - 7; + } + + if (is_joint) { + BLI_snprintf(rna_path, sizeof(rna_path), "%s.%s", joint_path, tm_str); + } + else { + BLI_strncpy(rna_path, tm_str, sizeof(rna_path)); + } + newcu[i] = create_fcurve(axis, rna_path); + newcu[i]->totvert = frames.size(); + } + + if (frames.size() == 0) { + return; + } + + std::sort(frames.begin(), frames.end()); + + std::vector<float>::iterator it; + +#if 0 + float qref[4]; + unit_qt(qref); +#endif + + /* sample values at each frame */ + for (it = frames.begin(); it != frames.end(); it++) { + float fra = *it; + + float mat[4][4]; + float matfra[4][4]; + + unit_m4(matfra); + + /* calc object-space mat */ + evaluate_transform_at_frame(matfra, node, fra); + + /* for joints, we need a special matrix */ + if (is_joint) { + /* special matrix: iR * M * iR_dae * R + * where R, iR are bone rest and inverse rest mats in world space (Blender bones), + * iR_dae is joint inverse rest matrix (DAE) + * and M is an evaluated joint world-space matrix (DAE) */ + float temp[4][4], par[4][4]; + + /* calc M */ + calc_joint_parent_mat_rest(par, NULL, root, node); + mul_m4_m4m4(temp, par, matfra); + +#if 0 + evaluate_joint_world_transform_at_frame(temp, NULL, node, fra); +#endif + + /* calc special matrix */ + mul_m4_series(mat, irest, temp, irest_dae, rest); + } + else { + copy_m4_m4(mat, matfra); + } + + float rot[4], loc[3], scale[3]; + mat4_decompose(loc, rot, scale, mat); + + /* add keys */ + for (int i = 0; i < totcu; i++) { + if (i < 4) { + add_bezt(newcu[i], fra, rot[i]); + } + else if (i < 7) { + add_bezt(newcu[i], fra, loc[i - 4]); + } + else { + add_bezt(newcu[i], fra, scale[i - 7]); + } + } + } + Main *bmain = CTX_data_main(mContext); + ED_id_action_ensure(bmain, (ID *)&ob->id); + + ListBase *curves = &ob->adt->action->curves; + + /* add curves */ + for (int i = 0; i < totcu; i++) { + if (is_joint) { + add_bone_fcurve(ob, node, newcu[i]); + } + else { + BLI_addtail(curves, newcu[i]); + } +#if 0 + fcurve_is_used(newcu[i]); /* never added to unused */ +#endif + } + + if (is_joint) { + bPoseChannel *chan = BKE_pose_channel_find_name(ob->pose, bone_name); + chan->rotmode = ROT_MODE_QUAT; + } + else { + ob->rotmode = ROT_MODE_QUAT; + } + + return; +} + +/* + * This function returns the aspect ration from the Collada camera. + * + * Note:COLLADA allows to specify either XFov, or YFov alone. + * In that case the aspect ratio can be determined from + * the viewport aspect ratio (which is 1:1 ?) + * XXX: check this: its probably wrong! + * If both values are specified, then the aspect ration is simply xfov/yfov + * and if aspect ratio is efined, then .. well then its that one. + */ +static const double get_aspect_ratio(const COLLADAFW::Camera *camera) +{ + double aspect = camera->getAspectRatio().getValue(); + + if (aspect == 0) { + const double yfov = camera->getYFov().getValue(); + + if (yfov == 0) { + aspect = 1; /* assume yfov and xfov are equal */ + } + else { + const double xfov = camera->getXFov().getValue(); + if (xfov == 0) { + aspect = 1; + } + else { + aspect = xfov / yfov; + } + } + } + return aspect; +} + +static ListBase &get_animation_curves(Main *bmain, Material *ma) +{ + bAction *act; + if (!ma->adt || !ma->adt->action) { + act = ED_id_action_ensure(bmain, (ID *)&ma->id); + } + else { + act = ma->adt->action; + } + + return act->curves; +} + +void AnimationImporter::translate_Animations( + COLLADAFW::Node *node, + std::map<COLLADAFW::UniqueId, COLLADAFW::Node *> &root_map, + std::multimap<COLLADAFW::UniqueId, Object *> &object_map, + std::map<COLLADAFW::UniqueId, const COLLADAFW::Object *> FW_object_map, + std::map<COLLADAFW::UniqueId, Material *> uid_material_map) +{ + bool is_joint = node->getType() == COLLADAFW::Node::JOINT; + COLLADAFW::UniqueId uid = node->getUniqueId(); + COLLADAFW::Node *root = root_map.find(uid) == root_map.end() ? node : root_map[uid]; + + Object *ob; + if (is_joint) { + ob = armature_importer->get_armature_for_joint(root); + } + else { + ob = object_map.find(uid) == object_map.end() ? NULL : object_map.find(uid)->second; + } + + if (!ob) { + fprintf(stderr, "cannot find Object for Node with id=\"%s\"\n", node->getOriginalId().c_str()); + return; + } + + AnimationImporter::AnimMix *animType = get_animation_type(node, FW_object_map); + bAction *act; + Main *bmain = CTX_data_main(mContext); + + if ((animType->transform) != 0) { + /* const char *bone_name = is_joint ? bc_get_joint_name(node) : NULL; */ /* UNUSED */ + char joint_path[200]; + + if (is_joint) { + armature_importer->get_rna_path_for_joint(node, joint_path, sizeof(joint_path)); + } + + if (!ob->adt || !ob->adt->action) { + act = ED_id_action_ensure(bmain, (ID *)&ob->id); + } + else { + act = ob->adt->action; + } + + /* Get the list of animation curves of the object */ + ListBase *AnimCurves = &(act->curves); + + const COLLADAFW::TransformationPointerArray &nodeTransforms = node->getTransformations(); + + /* for each transformation in node */ + for (unsigned int i = 0; i < nodeTransforms.getCount(); i++) { + COLLADAFW::Transformation *transform = nodeTransforms[i]; + COLLADAFW::Transformation::TransformationType tm_type = transform->getTransformationType(); + + bool is_rotation = tm_type == COLLADAFW::Transformation::ROTATE; + bool is_matrix = tm_type == COLLADAFW::Transformation::MATRIX; + + const COLLADAFW::UniqueId &listid = transform->getAnimationList(); + + /* check if transformation has animations */ + if (animlist_map.find(listid) == animlist_map.end()) { + continue; + } + else { + /* transformation has animations */ + const COLLADAFW::AnimationList *animlist = animlist_map[listid]; + const COLLADAFW::AnimationList::AnimationBindings &bindings = + animlist->getAnimationBindings(); + /* all the curves belonging to the current binding */ + std::vector<FCurve *> animcurves; + for (unsigned int j = 0; j < bindings.getCount(); j++) { + animcurves = curve_map[bindings[j].animation]; + if (is_matrix) { + apply_matrix_curves(ob, animcurves, root, node, transform); + } + else { + /* calculate rnapaths and array index of fcurves according to transformation and + * animation class */ + Assign_transform_animations( + transform, &bindings[j], &animcurves, is_joint, joint_path); + + std::vector<FCurve *>::iterator iter; + /* Add the curves of the current animation to the object */ + for (iter = animcurves.begin(); iter != animcurves.end(); iter++) { + FCurve *fcu = *iter; + + BLI_addtail(AnimCurves, fcu); + fcurve_is_used(fcu); + } + } + } + } + if (is_rotation && !(is_joint || is_matrix)) { + ob->rotmode = ROT_MODE_EUL; + } + } + } + + if ((animType->light) != 0) { + Light *lamp = (Light *)ob->data; + if (!lamp->adt || !lamp->adt->action) { + act = ED_id_action_ensure(bmain, (ID *)&lamp->id); + } + else { + act = lamp->adt->action; + } + + ListBase *AnimCurves = &(act->curves); + const COLLADAFW::InstanceLightPointerArray &nodeLights = node->getInstanceLights(); + + for (unsigned int i = 0; i < nodeLights.getCount(); i++) { + const COLLADAFW::Light *light = (COLLADAFW::Light *) + FW_object_map[nodeLights[i]->getInstanciatedObjectId()]; + + if ((animType->light & LIGHT_COLOR) != 0) { + const COLLADAFW::Color *col = &(light->getColor()); + const COLLADAFW::UniqueId &listid = col->getAnimationList(); + + Assign_color_animations(listid, AnimCurves, "color"); + } + if ((animType->light & LIGHT_FOA) != 0) { + const COLLADAFW::AnimatableFloat *foa = &(light->getFallOffAngle()); + const COLLADAFW::UniqueId &listid = foa->getAnimationList(); + + Assign_float_animations(listid, AnimCurves, "spot_size"); + } + if ((animType->light & LIGHT_FOE) != 0) { + const COLLADAFW::AnimatableFloat *foe = &(light->getFallOffExponent()); + const COLLADAFW::UniqueId &listid = foe->getAnimationList(); + + Assign_float_animations(listid, AnimCurves, "spot_blend"); + } + } + } + + if (animType->camera != 0) { + + Camera *cam = (Camera *)ob->data; + if (!cam->adt || !cam->adt->action) { + act = ED_id_action_ensure(bmain, (ID *)&cam->id); + } + else { + act = cam->adt->action; + } + + ListBase *AnimCurves = &(act->curves); + const COLLADAFW::InstanceCameraPointerArray &nodeCameras = node->getInstanceCameras(); + + for (unsigned int i = 0; i < nodeCameras.getCount(); i++) { + const COLLADAFW::Camera *camera = (COLLADAFW::Camera *) + FW_object_map[nodeCameras[i]->getInstanciatedObjectId()]; + + if ((animType->camera & CAMERA_XFOV) != 0) { + const COLLADAFW::AnimatableFloat *xfov = &(camera->getXFov()); + const COLLADAFW::UniqueId &listid = xfov->getAnimationList(); + double aspect = get_aspect_ratio(camera); + Assign_lens_animations(listid, AnimCurves, aspect, cam, "lens", CAMERA_XFOV); + } + + else if ((animType->camera & CAMERA_YFOV) != 0) { + const COLLADAFW::AnimatableFloat *yfov = &(camera->getYFov()); + const COLLADAFW::UniqueId &listid = yfov->getAnimationList(); + double aspect = get_aspect_ratio(camera); + Assign_lens_animations(listid, AnimCurves, aspect, cam, "lens", CAMERA_YFOV); + } + + else if ((animType->camera & CAMERA_XMAG) != 0) { + const COLLADAFW::AnimatableFloat *xmag = &(camera->getXMag()); + const COLLADAFW::UniqueId &listid = xmag->getAnimationList(); + Assign_float_animations(listid, AnimCurves, "ortho_scale"); + } + + else if ((animType->camera & CAMERA_YMAG) != 0) { + const COLLADAFW::AnimatableFloat *ymag = &(camera->getYMag()); + const COLLADAFW::UniqueId &listid = ymag->getAnimationList(); + Assign_float_animations(listid, AnimCurves, "ortho_scale"); + } + + if ((animType->camera & CAMERA_ZFAR) != 0) { + const COLLADAFW::AnimatableFloat *zfar = &(camera->getFarClippingPlane()); + const COLLADAFW::UniqueId &listid = zfar->getAnimationList(); + Assign_float_animations(listid, AnimCurves, "clip_end"); + } + + if ((animType->camera & CAMERA_ZNEAR) != 0) { + const COLLADAFW::AnimatableFloat *znear = &(camera->getNearClippingPlane()); + const COLLADAFW::UniqueId &listid = znear->getAnimationList(); + Assign_float_animations(listid, AnimCurves, "clip_start"); + } + } + } + if (animType->material != 0) { + + Material *ma = BKE_object_material_get(ob, 1); + if (!ma->adt || !ma->adt->action) { + act = ED_id_action_ensure(bmain, (ID *)&ma->id); + } + else { + act = ma->adt->action; + } + + const COLLADAFW::InstanceGeometryPointerArray &nodeGeoms = node->getInstanceGeometries(); + for (unsigned int i = 0; i < nodeGeoms.getCount(); i++) { + const COLLADAFW::MaterialBindingArray &matBinds = nodeGeoms[i]->getMaterialBindings(); + for (unsigned int j = 0; j < matBinds.getCount(); j++) { + const COLLADAFW::UniqueId &matuid = matBinds[j].getReferencedMaterial(); + const COLLADAFW::Effect *ef = (COLLADAFW::Effect *)(FW_object_map[matuid]); + if (ef != NULL) { /* can be NULL [#28909] */ + Material *ma = uid_material_map[matuid]; + if (!ma) { + fprintf(stderr, + "Collada: Node %s refers to undefined material\n", + node->getName().c_str()); + continue; + } + ListBase &AnimCurves = get_animation_curves(bmain, ma); + const COLLADAFW::CommonEffectPointerArray &commonEffects = ef->getCommonEffects(); + COLLADAFW::EffectCommon *efc = commonEffects[0]; + if ((animType->material & MATERIAL_SHININESS) != 0) { + const COLLADAFW::FloatOrParam *shin = &(efc->getShininess()); + const COLLADAFW::UniqueId &listid = shin->getAnimationList(); + Assign_float_animations(listid, &AnimCurves, "specular_hardness"); + } + + if ((animType->material & MATERIAL_IOR) != 0) { + const COLLADAFW::FloatOrParam *ior = &(efc->getIndexOfRefraction()); + const COLLADAFW::UniqueId &listid = ior->getAnimationList(); + Assign_float_animations(listid, &AnimCurves, "raytrace_transparency.ior"); + } + + if ((animType->material & MATERIAL_SPEC_COLOR) != 0) { + const COLLADAFW::ColorOrTexture *cot = &(efc->getSpecular()); + const COLLADAFW::UniqueId &listid = cot->getColor().getAnimationList(); + Assign_color_animations(listid, &AnimCurves, "specular_color"); + } + + if ((animType->material & MATERIAL_DIFF_COLOR) != 0) { + const COLLADAFW::ColorOrTexture *cot = &(efc->getDiffuse()); + const COLLADAFW::UniqueId &listid = cot->getColor().getAnimationList(); + Assign_color_animations(listid, &AnimCurves, "diffuse_color"); + } + } + } + } + } + + delete animType; +} + +void AnimationImporter::add_bone_animation_sampled(Object *ob, + std::vector<FCurve *> &animcurves, + COLLADAFW::Node *root, + COLLADAFW::Node *node, + COLLADAFW::Transformation *tm) +{ + const char *bone_name = bc_get_joint_name(node); + char joint_path[200]; + armature_importer->get_rna_path_for_joint(node, joint_path, sizeof(joint_path)); + + std::vector<float> frames; + find_frames(&frames, &animcurves); + + /* convert degrees to radians */ + if (tm->getTransformationType() == COLLADAFW::Transformation::ROTATE) { + + std::vector<FCurve *>::iterator iter; + for (iter = animcurves.begin(); iter != animcurves.end(); iter++) { + FCurve *fcu = *iter; + + fcurve_deg_to_rad(fcu); + } + } + + float irest_dae[4][4]; + float rest[4][4], irest[4][4]; + + get_joint_rest_mat(irest_dae, root, node); + invert_m4(irest_dae); + + Bone *bone = BKE_armature_find_bone_name((bArmature *)ob->data, bone_name); + if (!bone) { + fprintf(stderr, "cannot find bone \"%s\"\n", bone_name); + return; + } + + unit_m4(rest); + copy_m4_m4(rest, bone->arm_mat); + invert_m4_m4(irest, rest); + + /* new curves to assign matrix transform animation */ + FCurve *newcu[10]; /* if tm_type is matrix, then create 10 curves: 4 rot, 3 loc, 3 scale. */ + unsigned int totcu = 10; + const char *tm_str = NULL; + char rna_path[200]; + for (int i = 0; i < totcu; i++) { + + int axis = i; + + if (i < 4) { + tm_str = "rotation_quaternion"; + axis = i; + } + else if (i < 7) { + tm_str = "location"; + axis = i - 4; + } + else { + tm_str = "scale"; + axis = i - 7; + } + + BLI_snprintf(rna_path, sizeof(rna_path), "%s.%s", joint_path, tm_str); + + newcu[i] = create_fcurve(axis, rna_path); + newcu[i]->totvert = frames.size(); + } + + if (frames.size() == 0) { + return; + } + + std::sort(frames.begin(), frames.end()); + + BCQuat qref; + + std::vector<float>::iterator it; + + /* sample values at each frame */ + for (it = frames.begin(); it != frames.end(); it++) { + float fra = *it; + + Matrix mat; + Matrix matfra; + + unit_m4(matfra); + + /* calc object-space mat */ + evaluate_transform_at_frame(matfra, node, fra); + + /* for joints, we need a special matrix + * special matrix: iR * M * iR_dae * R + * where R, iR are bone rest and inverse rest mats in world space (Blender bones), + * iR_dae is joint inverse rest matrix (DAE) + * and M is an evaluated joint world-space matrix (DAE). */ + Matrix temp, par; + + /* calc M */ + calc_joint_parent_mat_rest(par, NULL, root, node); + mul_m4_m4m4(temp, par, matfra); + + /* evaluate_joint_world_transform_at_frame(temp, NULL, node, fra); */ + + /* calc special matrix */ + mul_m4_series(mat, irest, temp, irest_dae, rest); + + Vector loc, scale; + + qref.rotate_to(mat); + + copy_v3_v3(loc, mat[3]); + mat4_to_size(scale, mat); + + /* add keys */ + for (int i = 0; i < totcu; i++) { + if (i < 4) { + add_bezt(newcu[i], fra, qref.quat()[i]); + } + else if (i < 7) { + add_bezt(newcu[i], fra, loc[i - 4]); + } + else { + add_bezt(newcu[i], fra, scale[i - 7]); + } + } + } + Main *bmain = CTX_data_main(mContext); + ED_id_action_ensure(bmain, (ID *)&ob->id); + + /* add curves */ + for (int i = 0; i < totcu; i++) { + add_bone_fcurve(ob, node, newcu[i]); +#if 0 + fcurve_is_used(newcu[i]); /* never added to unused */ +#endif + } + + bPoseChannel *chan = BKE_pose_channel_find_name(ob->pose, bone_name); + chan->rotmode = ROT_MODE_QUAT; +} + +/* Check if object is animated by checking if animlist_map + * holds the animlist_id of node transforms */ +AnimationImporter::AnimMix *AnimationImporter::get_animation_type( + const COLLADAFW::Node *node, + std::map<COLLADAFW::UniqueId, const COLLADAFW::Object *> FW_object_map) +{ + AnimMix *types = new AnimMix(); + + const COLLADAFW::TransformationPointerArray &nodeTransforms = node->getTransformations(); + + /* for each transformation in node */ + for (unsigned int i = 0; i < nodeTransforms.getCount(); i++) { + COLLADAFW::Transformation *transform = nodeTransforms[i]; + const COLLADAFW::UniqueId &listid = transform->getAnimationList(); + + /* check if transformation has animations */ + if (animlist_map.find(listid) == animlist_map.end()) { + continue; + } + else { + types->transform = types->transform | BC_NODE_TRANSFORM; + break; + } + } + const COLLADAFW::InstanceLightPointerArray &nodeLights = node->getInstanceLights(); + + for (unsigned int i = 0; i < nodeLights.getCount(); i++) { + const COLLADAFW::Light *light = (COLLADAFW::Light *) + FW_object_map[nodeLights[i]->getInstanciatedObjectId()]; + types->light = setAnimType(&(light->getColor()), (types->light), LIGHT_COLOR); + types->light = setAnimType(&(light->getFallOffAngle()), (types->light), LIGHT_FOA); + types->light = setAnimType(&(light->getFallOffExponent()), (types->light), LIGHT_FOE); + + if (types->light != 0) { + break; + } + } + + const COLLADAFW::InstanceCameraPointerArray &nodeCameras = node->getInstanceCameras(); + for (unsigned int i = 0; i < nodeCameras.getCount(); i++) { + const COLLADAFW::Camera *camera = (COLLADAFW::Camera *) + FW_object_map[nodeCameras[i]->getInstanciatedObjectId()]; + if (camera == NULL) { + /* Can happen if the node refers to an unknown camera. */ + continue; + } + + const bool is_perspective_type = camera->getCameraType() == COLLADAFW::Camera::PERSPECTIVE; + + int addition; + const COLLADAFW::Animatable *mag; + const COLLADAFW::UniqueId listid = camera->getYMag().getAnimationList(); + if (animlist_map.find(listid) != animlist_map.end()) { + mag = &(camera->getYMag()); + addition = (is_perspective_type) ? CAMERA_YFOV : CAMERA_YMAG; + } + else { + mag = &(camera->getXMag()); + addition = (is_perspective_type) ? CAMERA_XFOV : CAMERA_XMAG; + } + types->camera = setAnimType(mag, (types->camera), addition); + + types->camera = setAnimType(&(camera->getFarClippingPlane()), (types->camera), CAMERA_ZFAR); + types->camera = setAnimType(&(camera->getNearClippingPlane()), (types->camera), CAMERA_ZNEAR); + + if (types->camera != 0) { + break; + } + } + + const COLLADAFW::InstanceGeometryPointerArray &nodeGeoms = node->getInstanceGeometries(); + for (unsigned int i = 0; i < nodeGeoms.getCount(); i++) { + const COLLADAFW::MaterialBindingArray &matBinds = nodeGeoms[i]->getMaterialBindings(); + for (unsigned int j = 0; j < matBinds.getCount(); j++) { + const COLLADAFW::UniqueId &matuid = matBinds[j].getReferencedMaterial(); + const COLLADAFW::Effect *ef = (COLLADAFW::Effect *)(FW_object_map[matuid]); + if (ef != NULL) { /* can be NULL [#28909] */ + const COLLADAFW::CommonEffectPointerArray &commonEffects = ef->getCommonEffects(); + if (!commonEffects.empty()) { + COLLADAFW::EffectCommon *efc = commonEffects[0]; + types->material = setAnimType( + &(efc->getShininess()), (types->material), MATERIAL_SHININESS); + types->material = setAnimType( + &(efc->getSpecular().getColor()), (types->material), MATERIAL_SPEC_COLOR); + types->material = setAnimType( + &(efc->getDiffuse().getColor()), (types->material), MATERIAL_DIFF_COLOR); +#if 0 + types->material = setAnimType(&(efc->get()), (types->material), MATERIAL_TRANSPARENCY); +#endif + types->material = setAnimType( + &(efc->getIndexOfRefraction()), (types->material), MATERIAL_IOR); + } + } + } + } + return types; +} + +int AnimationImporter::setAnimType(const COLLADAFW::Animatable *prop, int types, int addition) +{ + int anim_type; + const COLLADAFW::UniqueId &listid = prop->getAnimationList(); + if (animlist_map.find(listid) != animlist_map.end()) { + anim_type = types | addition; + } + else { + anim_type = types; + } + + return anim_type; +} + +/* Is not used anymore. */ +void AnimationImporter::find_frames_old(std::vector<float> *frames, + COLLADAFW::Node *node, + COLLADAFW::Transformation::TransformationType tm_type) +{ + bool is_matrix = tm_type == COLLADAFW::Transformation::MATRIX; + bool is_rotation = tm_type == COLLADAFW::Transformation::ROTATE; + /* for each <rotate>, <translate>, etc. there is a separate Transformation */ + const COLLADAFW::TransformationPointerArray &nodeTransforms = node->getTransformations(); + + unsigned int i; + /* find frames at which to sample plus convert all rotation keys to radians */ + for (i = 0; i < nodeTransforms.getCount(); i++) { + COLLADAFW::Transformation *transform = nodeTransforms[i]; + COLLADAFW::Transformation::TransformationType nodeTmType = transform->getTransformationType(); + + if (nodeTmType == tm_type) { + /* get animation bindings for the current transformation */ + const COLLADAFW::UniqueId &listid = transform->getAnimationList(); + /* if transform is animated its animlist must exist. */ + if (animlist_map.find(listid) != animlist_map.end()) { + + const COLLADAFW::AnimationList *animlist = animlist_map[listid]; + const COLLADAFW::AnimationList::AnimationBindings &bindings = + animlist->getAnimationBindings(); + + if (bindings.getCount()) { + /* for each AnimationBinding get the fcurves which animate the transform */ + for (unsigned int j = 0; j < bindings.getCount(); j++) { + std::vector<FCurve *> &curves = curve_map[bindings[j].animation]; + bool xyz = ((nodeTmType == COLLADAFW::Transformation::TRANSLATE || + nodeTmType == COLLADAFW::Transformation::SCALE) && + bindings[j].animationClass == COLLADAFW::AnimationList::POSITION_XYZ); + + if ((!xyz && curves.size() == 1) || (xyz && curves.size() == 3) || is_matrix) { + std::vector<FCurve *>::iterator iter; + + for (iter = curves.begin(); iter != curves.end(); iter++) { + FCurve *fcu = *iter; + + /* if transform is rotation the fcurves values must be turned in to radian. */ + if (is_rotation) { + fcurve_deg_to_rad(fcu); + } + + for (unsigned int k = 0; k < fcu->totvert; k++) { + /* get frame value from bezTriple */ + float fra = fcu->bezt[k].vec[1][0]; + /* if frame already not added add frame to frames */ + if (std::find(frames->begin(), frames->end(), fra) == frames->end()) { + frames->push_back(fra); + } + } + } + } + else { + fprintf(stderr, "expected %d curves, got %d\n", xyz ? 3 : 1, (int)curves.size()); + } + } + } + } + } + } +} + +/* prerequisites: + * animlist_map - map animlist id -> animlist + * curve_map - map anim id -> curve(s) */ +Object *AnimationImporter::translate_animation_OLD( + COLLADAFW::Node *node, + std::map<COLLADAFW::UniqueId, Object *> &object_map, + std::map<COLLADAFW::UniqueId, COLLADAFW::Node *> &root_map, + COLLADAFW::Transformation::TransformationType tm_type, + Object *par_job) +{ + + bool is_rotation = tm_type == COLLADAFW::Transformation::ROTATE; + bool is_matrix = tm_type == COLLADAFW::Transformation::MATRIX; + bool is_joint = node->getType() == COLLADAFW::Node::JOINT; + + COLLADAFW::Node *root = root_map.find(node->getUniqueId()) == root_map.end() ? + node : + root_map[node->getUniqueId()]; + Object *ob = is_joint ? armature_importer->get_armature_for_joint(node) : + object_map[node->getUniqueId()]; + const char *bone_name = is_joint ? bc_get_joint_name(node) : NULL; + if (!ob) { + fprintf(stderr, "cannot find Object for Node with id=\"%s\"\n", node->getOriginalId().c_str()); + return NULL; + } + + /* frames at which to sample */ + std::vector<float> frames; + + find_frames_old(&frames, node, tm_type); + + unsigned int i; + + float irest_dae[4][4]; + float rest[4][4], irest[4][4]; + + if (is_joint) { + get_joint_rest_mat(irest_dae, root, node); + invert_m4(irest_dae); + + Bone *bone = BKE_armature_find_bone_name((bArmature *)ob->data, bone_name); + if (!bone) { + fprintf(stderr, "cannot find bone \"%s\"\n", bone_name); + return NULL; + } + + unit_m4(rest); + copy_m4_m4(rest, bone->arm_mat); + invert_m4_m4(irest, rest); + } + + Object *job = NULL; + +#ifdef ARMATURE_TEST + FCurve *job_curves[10]; + job = get_joint_object(root, node, par_job); +#endif + + if (frames.size() == 0) { + return job; + } + + std::sort(frames.begin(), frames.end()); + + const char *tm_str = NULL; + switch (tm_type) { + case COLLADAFW::Transformation::ROTATE: + tm_str = "rotation_quaternion"; + break; + case COLLADAFW::Transformation::SCALE: + tm_str = "scale"; + break; + case COLLADAFW::Transformation::TRANSLATE: + tm_str = "location"; + break; + case COLLADAFW::Transformation::MATRIX: + break; + default: + return job; + } + + char rna_path[200]; + char joint_path[200]; + + if (is_joint) { + armature_importer->get_rna_path_for_joint(node, joint_path, sizeof(joint_path)); + } + + /* new curves */ + FCurve *newcu[10]; /* if tm_type is matrix, then create 10 curves: 4 rot, 3 loc, 3 scale */ + unsigned int totcu = is_matrix ? 10 : (is_rotation ? 4 : 3); + + for (i = 0; i < totcu; i++) { + + int axis = i; + + if (is_matrix) { + if (i < 4) { + tm_str = "rotation_quaternion"; + axis = i; + } + else if (i < 7) { + tm_str = "location"; + axis = i - 4; + } + else { + tm_str = "scale"; + axis = i - 7; + } + } + + if (is_joint) { + BLI_snprintf(rna_path, sizeof(rna_path), "%s.%s", joint_path, tm_str); + } + else { + BLI_strncpy(rna_path, tm_str, sizeof(rna_path)); + } + newcu[i] = create_fcurve(axis, rna_path); + +#ifdef ARMATURE_TEST + if (is_joint) { + job_curves[i] = create_fcurve(axis, tm_str); + } +#endif + } + + std::vector<float>::iterator it; + + /* sample values at each frame */ + for (it = frames.begin(); it != frames.end(); it++) { + float fra = *it; + + float mat[4][4]; + float matfra[4][4]; + + unit_m4(matfra); + + /* calc object-space mat */ + evaluate_transform_at_frame(matfra, node, fra); + + /* for joints, we need a special matrix */ + if (is_joint) { + /* special matrix: iR * M * iR_dae * R + * where R, iR are bone rest and inverse rest mats in world space (Blender bones), + * iR_dae is joint inverse rest matrix (DAE) + * and M is an evaluated joint world-space matrix (DAE). */ + float temp[4][4], par[4][4]; + + /* calc M */ + calc_joint_parent_mat_rest(par, NULL, root, node); + mul_m4_m4m4(temp, par, matfra); + + /* evaluate_joint_world_transform_at_frame(temp, NULL, node, fra); */ + + /* calc special matrix */ + mul_m4_series(mat, irest, temp, irest_dae, rest); + } + else { + copy_m4_m4(mat, matfra); + } + + float val[4] = {}; + float rot[4], loc[3], scale[3]; + + switch (tm_type) { + case COLLADAFW::Transformation::ROTATE: + mat4_to_quat(val, mat); + break; + case COLLADAFW::Transformation::SCALE: + mat4_to_size(val, mat); + break; + case COLLADAFW::Transformation::TRANSLATE: + copy_v3_v3(val, mat[3]); + break; + case COLLADAFW::Transformation::MATRIX: + mat4_to_quat(rot, mat); + copy_v3_v3(loc, mat[3]); + mat4_to_size(scale, mat); + break; + default: + break; + } + + /* add keys */ + for (i = 0; i < totcu; i++) { + if (is_matrix) { + if (i < 4) { + add_bezt(newcu[i], fra, rot[i]); + } + else if (i < 7) { + add_bezt(newcu[i], fra, loc[i - 4]); + } + else { + add_bezt(newcu[i], fra, scale[i - 7]); + } + } + else { + add_bezt(newcu[i], fra, val[i]); + } + } + +#ifdef ARMATURE_TEST + if (is_joint) { + switch (tm_type) { + case COLLADAFW::Transformation::ROTATE: + mat4_to_quat(val, matfra); + break; + case COLLADAFW::Transformation::SCALE: + mat4_to_size(val, matfra); + break; + case COLLADAFW::Transformation::TRANSLATE: + copy_v3_v3(val, matfra[3]); + break; + case MATRIX: + mat4_to_quat(rot, matfra); + copy_v3_v3(loc, matfra[3]); + mat4_to_size(scale, matfra); + break; + default: + break; + } + + for (i = 0; i < totcu; i++) { + if (is_matrix) { + if (i < 4) { + add_bezt(job_curves[i], fra, rot[i]); + } + else if (i < 7) { + add_bezt(job_curves[i], fra, loc[i - 4]); + } + else { + add_bezt(job_curves[i], fra, scale[i - 7]); + } + } + else { + add_bezt(job_curves[i], fra, val[i]); + } + } + } +#endif + } + Main *bmain = CTX_data_main(mContext); + ED_id_action_ensure(bmain, (ID *)&ob->id); + + ListBase *curves = &ob->adt->action->curves; + + /* add curves */ + for (i = 0; i < totcu; i++) { + if (is_joint) { + add_bone_fcurve(ob, node, newcu[i]); + } + else { + BLI_addtail(curves, newcu[i]); + } + +#ifdef ARMATURE_TEST + if (is_joint) { + BLI_addtail(&job->adt->action->curves, job_curves[i]); + } +#endif + } + + if (is_rotation || is_matrix) { + if (is_joint) { + bPoseChannel *chan = BKE_pose_channel_find_name(ob->pose, bone_name); + chan->rotmode = (is_matrix) ? ROT_MODE_QUAT : ROT_MODE_EUL; + } + else { + ob->rotmode = (is_matrix) ? ROT_MODE_QUAT : ROT_MODE_EUL; + } + } + + return job; +} + +/* internal, better make it private + * warning: evaluates only rotation and only assigns matrix transforms now + * prerequisites: animlist_map, curve_map */ +void AnimationImporter::evaluate_transform_at_frame(float mat[4][4], + COLLADAFW::Node *node, + float fra) +{ + const COLLADAFW::TransformationPointerArray &tms = node->getTransformations(); + + unit_m4(mat); + + for (unsigned int i = 0; i < tms.getCount(); i++) { + COLLADAFW::Transformation *tm = tms[i]; + COLLADAFW::Transformation::TransformationType type = tm->getTransformationType(); + float m[4][4]; + + unit_m4(m); + + std::string nodename = node->getName().size() ? node->getName() : node->getOriginalId(); + if (!evaluate_animation(tm, m, fra, nodename.c_str())) { + switch (type) { + case COLLADAFW::Transformation::ROTATE: + dae_rotate_to_mat4(tm, m); + break; + case COLLADAFW::Transformation::TRANSLATE: + dae_translate_to_mat4(tm, m); + break; + case COLLADAFW::Transformation::SCALE: + dae_scale_to_mat4(tm, m); + break; + case COLLADAFW::Transformation::MATRIX: + dae_matrix_to_mat4(tm, m); + break; + default: + fprintf(stderr, "unsupported transformation type %d\n", type); + } + } + + float temp[4][4]; + copy_m4_m4(temp, mat); + + mul_m4_m4m4(mat, temp, m); + } +} + +static void report_class_type_unsupported(const char *path, + const COLLADAFW::AnimationList::AnimationClass animclass, + const COLLADAFW::Transformation::TransformationType type) +{ + if (animclass == COLLADAFW::AnimationList::UNKNOWN_CLASS) { + fprintf(stderr, "%s: UNKNOWN animation class\n", path); + } + else { + fprintf(stderr, + "%s: animation class %d is not supported yet for transformation type %d\n", + path, + animclass, + type); + } +} + +/* return true to indicate that mat contains a sane value */ +bool AnimationImporter::evaluate_animation(COLLADAFW::Transformation *tm, + float mat[4][4], + float fra, + const char *node_id) +{ + const COLLADAFW::UniqueId &listid = tm->getAnimationList(); + COLLADAFW::Transformation::TransformationType type = tm->getTransformationType(); + + if (type != COLLADAFW::Transformation::ROTATE && type != COLLADAFW::Transformation::SCALE && + type != COLLADAFW::Transformation::TRANSLATE && type != COLLADAFW::Transformation::MATRIX) { + fprintf(stderr, "animation of transformation %d is not supported yet\n", type); + return false; + } + + if (animlist_map.find(listid) == animlist_map.end()) { + return false; + } + + const COLLADAFW::AnimationList *animlist = animlist_map[listid]; + const COLLADAFW::AnimationList::AnimationBindings &bindings = animlist->getAnimationBindings(); + + if (bindings.getCount()) { + float vec[3]; + + bool is_scale = (type == COLLADAFW::Transformation::SCALE); + bool is_translate = (type == COLLADAFW::Transformation::TRANSLATE); + + if (is_scale) { + dae_scale_to_v3(tm, vec); + } + else if (is_translate) { + dae_translate_to_v3(tm, vec); + } + + for (unsigned int index = 0; index < bindings.getCount(); index++) { + const COLLADAFW::AnimationList::AnimationBinding &binding = bindings[index]; + std::vector<FCurve *> &curves = curve_map[binding.animation]; + COLLADAFW::AnimationList::AnimationClass animclass = binding.animationClass; + char path[100]; + + switch (type) { + case COLLADAFW::Transformation::ROTATE: + BLI_snprintf(path, sizeof(path), "%s.rotate (binding %u)", node_id, index); + break; + case COLLADAFW::Transformation::SCALE: + BLI_snprintf(path, sizeof(path), "%s.scale (binding %u)", node_id, index); + break; + case COLLADAFW::Transformation::TRANSLATE: + BLI_snprintf(path, sizeof(path), "%s.translate (binding %u)", node_id, index); + break; + case COLLADAFW::Transformation::MATRIX: + BLI_snprintf(path, sizeof(path), "%s.matrix (binding %u)", node_id, index); + break; + default: + break; + } + + if (type == COLLADAFW::Transformation::ROTATE) { + if (curves.size() != 1) { + fprintf(stderr, "expected 1 curve, got %d\n", (int)curves.size()); + return false; + } + + /* TODO support other animclasses */ + if (animclass != COLLADAFW::AnimationList::ANGLE) { + report_class_type_unsupported(path, animclass, type); + return false; + } + + COLLADABU::Math::Vector3 &axis = ((COLLADAFW::Rotate *)tm)->getRotationAxis(); + + float ax[3] = {(float)axis[0], (float)axis[1], (float)axis[2]}; + float angle = evaluate_fcurve(curves[0], fra); + axis_angle_to_mat4(mat, ax, angle); + + return true; + } + else if (is_scale || is_translate) { + bool is_xyz = animclass == COLLADAFW::AnimationList::POSITION_XYZ; + + if ((!is_xyz && curves.size() != 1) || (is_xyz && curves.size() != 3)) { + if (is_xyz) { + fprintf(stderr, "%s: expected 3 curves, got %d\n", path, (int)curves.size()); + } + else { + fprintf(stderr, "%s: expected 1 curve, got %d\n", path, (int)curves.size()); + } + return false; + } + + switch (animclass) { + case COLLADAFW::AnimationList::POSITION_X: + vec[0] = evaluate_fcurve(curves[0], fra); + break; + case COLLADAFW::AnimationList::POSITION_Y: + vec[1] = evaluate_fcurve(curves[0], fra); + break; + case COLLADAFW::AnimationList::POSITION_Z: + vec[2] = evaluate_fcurve(curves[0], fra); + break; + case COLLADAFW::AnimationList::POSITION_XYZ: + vec[0] = evaluate_fcurve(curves[0], fra); + vec[1] = evaluate_fcurve(curves[1], fra); + vec[2] = evaluate_fcurve(curves[2], fra); + break; + default: + report_class_type_unsupported(path, animclass, type); + break; + } + } + else if (type == COLLADAFW::Transformation::MATRIX) { + /* for now, of matrix animation, + * support only the case when all values are packed into one animation */ + if (curves.size() != 16) { + fprintf(stderr, "%s: expected 16 curves, got %d\n", path, (int)curves.size()); + return false; + } + + COLLADABU::Math::Matrix4 matrix; + int mi = 0, mj = 0; + + for (std::vector<FCurve *>::iterator it = curves.begin(); it != curves.end(); it++) { + matrix.setElement(mi, mj, evaluate_fcurve(*it, fra)); + mj++; + if (mj == 4) { + mi++; + mj = 0; + } + } + unit_converter->dae_matrix_to_mat4_(mat, matrix); + return true; + } + } + + if (is_scale) { + size_to_mat4(mat, vec); + } + else { + copy_v3_v3(mat[3], vec); + } + + return is_scale || is_translate; + } + + return false; +} + +/* gives a world-space mat of joint at rest position */ +void AnimationImporter::get_joint_rest_mat(float mat[4][4], + COLLADAFW::Node *root, + COLLADAFW::Node *node) +{ + /* if bind mat is not available, + * use "current" node transform, i.e. all those tms listed inside <node> */ + if (!armature_importer->get_joint_bind_mat(mat, node)) { + float par[4][4], m[4][4]; + + calc_joint_parent_mat_rest(par, NULL, root, node); + get_node_mat(m, node, NULL, NULL); + mul_m4_m4m4(mat, par, m); + } +} + +/* gives a world-space mat, end's mat not included */ +bool AnimationImporter::calc_joint_parent_mat_rest(float mat[4][4], + float par[4][4], + COLLADAFW::Node *node, + COLLADAFW::Node *end) +{ + float m[4][4]; + + if (node == end) { + par ? copy_m4_m4(mat, par) : unit_m4(mat); + return true; + } + + /* use bind matrix if available or calc "current" world mat */ + if (!armature_importer->get_joint_bind_mat(m, node)) { + if (par) { + float temp[4][4]; + get_node_mat(temp, node, NULL, NULL); + mul_m4_m4m4(m, par, temp); + } + else { + get_node_mat(m, node, NULL, NULL); + } + } + + COLLADAFW::NodePointerArray &children = node->getChildNodes(); + for (unsigned int i = 0; i < children.getCount(); i++) { + if (calc_joint_parent_mat_rest(mat, m, children[i], end)) { + return true; + } + } + + return false; +} + +#ifdef ARMATURE_TEST +Object *AnimationImporter::get_joint_object(COLLADAFW::Node *root, + COLLADAFW::Node *node, + Object *par_job) +{ + if (joint_objects.find(node->getUniqueId()) == joint_objects.end()) { + Object *job = bc_add_object(scene, OB_EMPTY, (char *)get_joint_name(node)); + + job->lay = BKE_scene_base_find(scene, job)->lay = 2; + + mul_v3_fl(job->scale, 0.5f); + DEG_id_tag_update(&job->id, ID_RECALC_TRANSFORM); + + ED_id_action_ensure((ID *)&job->id); + + job->rotmode = ROT_MODE_QUAT; + + float mat[4][4]; + get_joint_rest_mat(mat, root, node); + + if (par_job) { + float temp[4][4], ipar[4][4]; + invert_m4_m4(ipar, par_job->obmat); + copy_m4_m4(temp, mat); + mul_m4_m4m4(mat, ipar, temp); + } + + bc_decompose(mat, job->loc, NULL, job->quat, job->scale); + + if (par_job) { + job->parent = par_job; + + DEG_id_tag_update(&par_job->id, ID_RECALC_TRANSFORM); + job->parsubstr[0] = 0; + } + + BKE_object_where_is_calc(scene, job); + + /* after parenting and layer change */ + DEG_relations_tag_update(CTX_data_main(C)); + + joint_objects[node->getUniqueId()] = job; + } + + return joint_objects[node->getUniqueId()]; +} +#endif + +#if 0 +/* recursively evaluates joint tree until end is found, + * mat then is world-space matrix of end mat must be identity on enter, node must be root. */ +bool AnimationImporter::evaluate_joint_world_transform_at_frame( + float mat[4][4], float par[4][4], COLLADAFW::Node *node, COLLADAFW::Node *end, float fra) +{ + float m[4][4]; + if (par) { + float temp[4][4]; + evaluate_transform_at_frame(temp, node, node == end ? fra : 0.0f); + mul_m4_m4m4(m, par, temp); + } + else { + evaluate_transform_at_frame(m, node, node == end ? fra : 0.0f); + } + + if (node == end) { + copy_m4_m4(mat, m); + return true; + } + else { + COLLADAFW::NodePointerArray &children = node->getChildNodes(); + for (int i = 0; i < children.getCount(); i++) { + if (evaluate_joint_world_transform_at_frame(mat, m, children[i], end, fra)) { + return true; + } + } + } + + return false; +} +#endif + +void AnimationImporter::add_bone_fcurve(Object *ob, COLLADAFW::Node *node, FCurve *fcu) +{ + const char *bone_name = bc_get_joint_name(node); + bAction *act = ob->adt->action; + + /* try to find group */ + bActionGroup *grp = BKE_action_group_find_name(act, bone_name); + + /* no matching groups, so add one */ + if (grp == NULL) { + /* Add a new group, and make it active */ + grp = (bActionGroup *)MEM_callocN(sizeof(bActionGroup), "bActionGroup"); + + grp->flag = AGRP_SELECTED; + BLI_strncpy(grp->name, bone_name, sizeof(grp->name)); + + BLI_addtail(&act->groups, grp); + BLI_uniquename(&act->groups, + grp, + CTX_DATA_(BLT_I18NCONTEXT_ID_ACTION, "Group"), + '.', + offsetof(bActionGroup, name), + 64); + } + + /* add F-Curve to group */ + action_groups_add_channel(act, grp, fcu); +} + +void AnimationImporter::set_import_from_version(std::string import_from_version) +{ + this->import_from_version = import_from_version; +} diff --git a/source/blender/io/collada/AnimationImporter.h b/source/blender/io/collada/AnimationImporter.h new file mode 100644 index 00000000000..0043dad7116 --- /dev/null +++ b/source/blender/io/collada/AnimationImporter.h @@ -0,0 +1,254 @@ +/* + * 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 collada + */ + +#ifndef __ANIMATIONIMPORTER_H__ +#define __ANIMATIONIMPORTER_H__ + +#include <map> +#include <vector> + +#include "COLLADAFWAnimation.h" +#include "COLLADAFWAnimationCurve.h" +#include "COLLADAFWAnimationList.h" +#include "COLLADAFWNode.h" +#include "COLLADAFWUniqueId.h" +#include "COLLADAFWLight.h" +#include "COLLADAFWCamera.h" +#include "COLLADAFWMaterial.h" +#include "COLLADAFWEffect.h" +#include "COLLADAFWInstanceGeometry.h" + +extern "C" { +#include "BKE_context.h" +#include "DNA_anim_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_light_types.h" +#include "DNA_camera_types.h" +} + +//#include "ArmatureImporter.h" +#include "TransformReader.h" + +#include "collada_internal.h" + +class ArmatureImporter; + +class AnimationImporterBase { + public: + // virtual void change_eul_to_quat(Object *ob, bAction *act) = 0; +}; + +class AnimationImporter : private TransformReader, public AnimationImporterBase { + private: + bContext *mContext; + ArmatureImporter *armature_importer; + Scene *scene; + + std::map<COLLADAFW::UniqueId, std::vector<FCurve *>> curve_map; + std::map<COLLADAFW::UniqueId, TransformReader::Animation> uid_animated_map; + // std::map<bActionGroup*, std::vector<FCurve*> > fcurves_actionGroup_map; + std::map<COLLADAFW::UniqueId, const COLLADAFW::AnimationList *> animlist_map; + std::vector<FCurve *> unused_curves; + std::map<COLLADAFW::UniqueId, Object *> joint_objects; + + FCurve *create_fcurve(int array_index, const char *rna_path); + + void add_bezt(FCurve *fcu, + float frame, + float value, + eBezTriple_Interpolation ipo = BEZT_IPO_LIN); + + // create one or several fcurves depending on the number of parameters being animated + void animation_to_fcurves(COLLADAFW::AnimationCurve *curve); + + void fcurve_deg_to_rad(FCurve *cu); + void fcurve_scale(FCurve *cu, int scale); + + void fcurve_is_used(FCurve *fcu); + + void add_fcurves_to_object(Main *bmain, + Object *ob, + std::vector<FCurve *> &curves, + char *rna_path, + int array_index, + Animation *animated); + + int typeFlag; + + std::string import_from_version; + + enum lightAnim { + // INANIMATE = 0, + LIGHT_COLOR = 2, + LIGHT_FOA = 4, + LIGHT_FOE = 8, + }; + + enum cameraAnim { + // INANIMATE = 0, + CAMERA_XFOV = 2, + CAMERA_XMAG = 4, + CAMERA_YFOV = 8, + CAMERA_YMAG = 16, + CAMERA_ZFAR = 32, + CAMERA_ZNEAR = 64, + }; + + enum matAnim { + MATERIAL_SHININESS = 2, + MATERIAL_SPEC_COLOR = 4, + MATERIAL_DIFF_COLOR = 1 << 3, + MATERIAL_TRANSPARENCY = 1 << 4, + MATERIAL_IOR = 1 << 5, + }; + + enum AnimationType { + BC_INANIMATE = 0, + BC_NODE_TRANSFORM = 1, + }; + + struct AnimMix { + int transform; + int light; + int camera; + int material; + int texture; + }; + + public: + AnimationImporter(bContext *C, UnitConverter *conv, ArmatureImporter *arm, Scene *scene) + : TransformReader(conv), mContext(C), armature_importer(arm), scene(scene) + { + } + + ~AnimationImporter(); + + void set_import_from_version(std::string import_from_version); + bool write_animation(const COLLADAFW::Animation *anim); + + // called on post-process stage after writeVisualScenes + bool write_animation_list(const COLLADAFW::AnimationList *animlist); + + void read_node_transform(COLLADAFW::Node *node, Object *ob); +#if 0 + virtual void change_eul_to_quat(Object *ob, bAction *act); +#endif + + void translate_Animations(COLLADAFW::Node *Node, + std::map<COLLADAFW::UniqueId, COLLADAFW::Node *> &root_map, + std::multimap<COLLADAFW::UniqueId, Object *> &object_map, + std::map<COLLADAFW::UniqueId, const COLLADAFW::Object *> FW_object_map, + std::map<COLLADAFW::UniqueId, Material *> uid_material_map); + + AnimMix *get_animation_type( + const COLLADAFW::Node *node, + std::map<COLLADAFW::UniqueId, const COLLADAFW::Object *> FW_object_map); + + void apply_matrix_curves(Object *ob, + std::vector<FCurve *> &animcurves, + COLLADAFW::Node *root, + COLLADAFW::Node *node, + COLLADAFW::Transformation *tm); + + void add_bone_animation_sampled(Object *ob, + std::vector<FCurve *> &animcurves, + COLLADAFW::Node *root, + COLLADAFW::Node *node, + COLLADAFW::Transformation *tm); + + void Assign_transform_animations(COLLADAFW::Transformation *transform, + const COLLADAFW::AnimationList::AnimationBinding *binding, + std::vector<FCurve *> *curves, + bool is_joint, + char *joint_path); + + void Assign_color_animations(const COLLADAFW::UniqueId &listid, + ListBase *AnimCurves, + const char *anim_type); + void Assign_float_animations(const COLLADAFW::UniqueId &listid, + ListBase *AnimCurves, + const char *anim_type); + void Assign_lens_animations(const COLLADAFW::UniqueId &listid, + ListBase *AnimCurves, + const double aspect, + Camera *cam, + const char *anim_type, + int fov_type); + + int setAnimType(const COLLADAFW::Animatable *prop, int type, int addition); + + void modify_fcurve(std::vector<FCurve *> *curves, + const char *rna_path, + int array_index, + int scale = 1); + void unused_fcurve(std::vector<FCurve *> *curves); + // prerequisites: + // animlist_map - map animlist id -> animlist + // curve_map - map anim id -> curve(s) + Object *translate_animation_OLD(COLLADAFW::Node *node, + std::map<COLLADAFW::UniqueId, Object *> &object_map, + std::map<COLLADAFW::UniqueId, COLLADAFW::Node *> &root_map, + COLLADAFW::Transformation::TransformationType tm_type, + Object *par_job = NULL); + + void find_frames(std::vector<float> *frames, std::vector<FCurve *> *curves); + void find_frames_old(std::vector<float> *frames, + COLLADAFW::Node *node, + COLLADAFW::Transformation::TransformationType tm_type); + // internal, better make it private + // warning: evaluates only rotation + // prerequisites: animlist_map, curve_map + void evaluate_transform_at_frame(float mat[4][4], COLLADAFW::Node *node, float fra); + + // return true to indicate that mat contains a sane value + bool evaluate_animation(COLLADAFW::Transformation *tm, + float mat[4][4], + float fra, + const char *node_id); + + // gives a world-space mat of joint at rest position + void get_joint_rest_mat(float mat[4][4], COLLADAFW::Node *root, COLLADAFW::Node *node); + + // gives a world-space mat, end's mat not included + bool calc_joint_parent_mat_rest(float mat[4][4], + float par[4][4], + COLLADAFW::Node *node, + COLLADAFW::Node *end); + + float convert_to_focal_length(float in_xfov, int fov_type, float aspect, float sensorx); + +#ifdef ARMATURE_TEST + Object *get_joint_object(COLLADAFW::Node *root, COLLADAFW::Node *node, Object *par_job); +#endif + +#if 0 + // recursively evaluates joint tree until end is found, mat then is world-space matrix of end + // mat must be identity on enter, node must be root + bool evaluate_joint_world_transform_at_frame( + float mat[4][4], float par[4][4], COLLADAFW::Node *node, COLLADAFW::Node *end, float fra); +#endif + + void add_bone_fcurve(Object *ob, COLLADAFW::Node *node, FCurve *fcu); + + void extra_data_importer(std::string elementName); +}; + +#endif diff --git a/source/blender/io/collada/ArmatureExporter.cpp b/source/blender/io/collada/ArmatureExporter.cpp new file mode 100644 index 00000000000..84979cc4ca4 --- /dev/null +++ b/source/blender/io/collada/ArmatureExporter.cpp @@ -0,0 +1,328 @@ +/* + * 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 collada + */ + +#include "COLLADASWBaseInputElement.h" +#include "COLLADASWInstanceController.h" +#include "COLLADASWPrimitves.h" +#include "COLLADASWSource.h" + +#include "DNA_action_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" + +#include "BKE_action.h" +#include "BKE_armature.h" + +extern "C" { +#include "BKE_global.h" +#include "BKE_mesh.h" +} + +#include "ED_armature.h" + +#include "BLI_listbase.h" + +#include "GeometryExporter.h" +#include "ArmatureExporter.h" +#include "SceneExporter.h" + +// write bone nodes +void ArmatureExporter::add_armature_bones(Object *ob_arm, + ViewLayer *view_layer, + SceneExporter *se, + std::vector<Object *> &child_objects) + +{ + // write bone nodes + + bArmature *armature = (bArmature *)ob_arm->data; + bool is_edited = armature->edbo != NULL; + + if (!is_edited) { + ED_armature_to_edit(armature); + } + + for (Bone *bone = (Bone *)armature->bonebase.first; bone; bone = bone->next) { + add_bone_node(bone, ob_arm, se, child_objects); + } + + if (!is_edited) { + ED_armature_edit_free(armature); + } +} + +void ArmatureExporter::write_bone_URLs(COLLADASW::InstanceController &ins, + Object *ob_arm, + Bone *bone) +{ + if (bc_is_root_bone(bone, this->export_settings.get_deform_bones_only())) { + std::string joint_id = translate_id(id_name(ob_arm) + "_" + bone->name); + ins.addSkeleton(COLLADABU::URI(COLLADABU::Utils::EMPTY_STRING, joint_id)); + } + else { + for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) { + write_bone_URLs(ins, ob_arm, child); + } + } +} + +bool ArmatureExporter::add_instance_controller(Object *ob) +{ + Object *ob_arm = bc_get_assigned_armature(ob); + bArmature *arm = (bArmature *)ob_arm->data; + + const std::string &controller_id = get_controller_id(ob_arm, ob); + + COLLADASW::InstanceController ins(mSW); + ins.setUrl(COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, controller_id)); + + Mesh *me = (Mesh *)ob->data; + if (!me->dvert) { + return false; + } + + // write root bone URLs + Bone *bone; + for (bone = (Bone *)arm->bonebase.first; bone; bone = bone->next) { + write_bone_URLs(ins, ob_arm, bone); + } + + InstanceWriter::add_material_bindings( + ins.getBindMaterial(), ob, this->export_settings.get_active_uv_only()); + + ins.add(); + return true; +} + +#if 0 +void ArmatureExporter::operator()(Object *ob) +{ + Object *ob_arm = bc_get_assigned_armature(ob); +} + +bool ArmatureExporter::already_written(Object *ob_arm) +{ + return std::find(written_armatures.begin(), written_armatures.end(), ob_arm) != + written_armatures.end(); +} + +void ArmatureExporter::wrote(Object *ob_arm) +{ + written_armatures.push_back(ob_arm); +} + +void ArmatureExporter::find_objects_using_armature(Object *ob_arm, + std::vector<Object *> &objects, + Scene *sce) +{ + objects.clear(); + + Base *base = (Base *)sce->base.first; + while (base) { + Object *ob = base->object; + + if (ob->type == OB_MESH && get_assigned_armature(ob) == ob_arm) { + objects.push_back(ob); + } + + base = base->next; + } +} +#endif + +// parent_mat is armature-space +void ArmatureExporter::add_bone_node(Bone *bone, + Object *ob_arm, + SceneExporter *se, + std::vector<Object *> &child_objects) +{ + if (can_export(bone)) { + std::string node_id = translate_id(id_name(ob_arm) + "_" + bone->name); + std::string node_name = std::string(bone->name); + std::string node_sid = get_joint_sid(bone); + + COLLADASW::Node node(mSW); + + node.setType(COLLADASW::Node::JOINT); + node.setNodeId(node_id); + node.setNodeName(node_name); + node.setNodeSid(node_sid); + + if (this->export_settings.get_use_blender_profile()) { + if (!is_export_root(bone)) { + if (bone->flag & BONE_CONNECTED) { + node.addExtraTechniqueParameter("blender", "connect", true); + } + } + std::string layers = BoneExtended::get_bone_layers(bone->layer); + node.addExtraTechniqueParameter("blender", "layer", layers); + + bArmature *armature = (bArmature *)ob_arm->data; + EditBone *ebone = bc_get_edit_bone(armature, bone->name); + if (ebone && ebone->roll != 0) { + node.addExtraTechniqueParameter("blender", "roll", ebone->roll); + } + if (bc_is_leaf_bone(bone)) { + Vector head, tail; + const BCMatrix &global_transform = this->export_settings.get_global_transform(); + if (this->export_settings.get_apply_global_orientation()) { + bc_add_global_transform(head, bone->arm_head, global_transform); + bc_add_global_transform(tail, bone->arm_tail, global_transform); + } + else { + copy_v3_v3(head, bone->arm_head); + copy_v3_v3(tail, bone->arm_tail); + } + node.addExtraTechniqueParameter("blender", "tip_x", tail[0] - head[0]); + node.addExtraTechniqueParameter("blender", "tip_y", tail[1] - head[1]); + node.addExtraTechniqueParameter("blender", "tip_z", tail[2] - head[2]); + } + } + + node.start(); + + add_bone_transform(ob_arm, bone, node); + + // Write nodes of childobjects, remove written objects from list + std::vector<Object *>::iterator iter = child_objects.begin(); + + while (iter != child_objects.end()) { + Object *ob = *iter; + if (ob->partype == PARBONE && STREQ(ob->parsubstr, bone->name)) { + float backup_parinv[4][4]; + copy_m4_m4(backup_parinv, ob->parentinv); + + // crude, temporary change to parentinv + // so transform gets exported correctly. + + // Add bone tail- translation... don't know why + // bone parenting is against the tail of a bone + // and not it's head, seems arbitrary. + ob->parentinv[3][1] += bone->length; + + // OPEN_SIM_COMPATIBILITY + // TODO: when such objects are animated as + // single matrix the tweak must be applied + // to the result. + if (export_settings.get_open_sim()) { + // tweak objects parentinverse to match compatibility + float temp[4][4]; + + copy_m4_m4(temp, bone->arm_mat); + temp[3][0] = temp[3][1] = temp[3][2] = 0.0f; + + mul_m4_m4m4(ob->parentinv, temp, ob->parentinv); + } + + se->writeNode(ob); + copy_m4_m4(ob->parentinv, backup_parinv); + iter = child_objects.erase(iter); + } + else { + iter++; + } + } + + for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) { + add_bone_node(child, ob_arm, se, child_objects); + } + node.end(); + } + else { + for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) { + add_bone_node(child, ob_arm, se, child_objects); + } + } +} + +bool ArmatureExporter::is_export_root(Bone *bone) +{ + Bone *entry = bone->parent; + while (entry) { + if (can_export(entry)) { + return false; + } + entry = entry->parent; + } + return can_export(bone); +} + +void ArmatureExporter::add_bone_transform(Object *ob_arm, Bone *bone, COLLADASW::Node &node) +{ + // bPoseChannel *pchan = BKE_pose_channel_find_name(ob_arm->pose, bone->name); + + float mat[4][4]; + float bone_rest_mat[4][4]; /* derived from bone->arm_mat */ + float parent_rest_mat[4][4]; /* derived from bone->parent->arm_mat */ + + bool has_restmat = bc_get_property_matrix(bone, "rest_mat", mat); + + if (!has_restmat) { + + /* Have no restpose matrix stored, try old style <= Blender 2.78 */ + + bc_create_restpose_mat(this->export_settings, bone, bone_rest_mat, bone->arm_mat, true); + + if (is_export_root(bone)) { + copy_m4_m4(mat, bone_rest_mat); + } + else { + Matrix parent_inverse; + bc_create_restpose_mat( + this->export_settings, bone->parent, parent_rest_mat, bone->parent->arm_mat, true); + + invert_m4_m4(parent_inverse, parent_rest_mat); + mul_m4_m4m4(mat, parent_inverse, bone_rest_mat); + } + + // OPEN_SIM_COMPATIBILITY + + if (export_settings.get_open_sim()) { + // Remove rotations vs armature from transform + // parent_rest_rot * mat * irest_rot + Matrix workmat; + copy_m4_m4(workmat, bone_rest_mat); + + workmat[3][0] = workmat[3][1] = workmat[3][2] = 0.0f; + invert_m4(workmat); + + mul_m4_m4m4(mat, mat, workmat); + + if (!is_export_root(bone)) { + copy_m4_m4(workmat, parent_rest_mat); + workmat[3][0] = workmat[3][1] = workmat[3][2] = 0.0f; + + mul_m4_m4m4(mat, workmat, mat); + } + } + } + + if (this->export_settings.get_limit_precision()) { + BCMatrix::sanitize(mat, LIMITTED_PRECISION); + } + + TransformWriter::add_joint_transform(node, mat, NULL, this->export_settings, has_restmat); +} + +std::string ArmatureExporter::get_controller_id(Object *ob_arm, Object *ob) +{ + return translate_id(id_name(ob_arm)) + "_" + translate_id(id_name(ob)) + + SKIN_CONTROLLER_ID_SUFFIX; +} diff --git a/source/blender/io/collada/ArmatureExporter.h b/source/blender/io/collada/ArmatureExporter.h new file mode 100644 index 00000000000..da6d6f79ef5 --- /dev/null +++ b/source/blender/io/collada/ArmatureExporter.h @@ -0,0 +1,107 @@ +/* + * 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 collada + */ + +#ifndef __ARMATUREEXPORTER_H__ +#define __ARMATUREEXPORTER_H__ + +#include <list> +#include <string> +//#include <vector> + +#include "COLLADASWStreamWriter.h" +#include "COLLADASWLibraryControllers.h" +#include "COLLADASWInputList.h" +#include "COLLADASWNode.h" + +#include "DNA_armature_types.h" +#include "DNA_listBase.h" +#include "DNA_mesh_types.h" +#include "DNA_object_types.h" +#include "DNA_constraint_types.h" +#include "DNA_scene_types.h" + +#include "TransformWriter.h" +#include "InstanceWriter.h" + +#include "ExportSettings.h" + +class SceneExporter; + +// XXX exporter writes wrong data for shared armatures. A separate +// controller should be written for each armature-mesh binding how do +// we make controller ids then? +class ArmatureExporter : public COLLADASW::LibraryControllers, + protected TransformWriter, + protected InstanceWriter { + public: + // XXX exporter writes wrong data for shared armatures. A separate + // controller should be written for each armature-mesh binding how do + // we make controller ids then? + ArmatureExporter(BlenderContext &blender_context, + COLLADASW::StreamWriter *sw, + BCExportSettings &export_settings) + : COLLADASW::LibraryControllers(sw), + blender_context(blender_context), + export_settings(export_settings) + { + } + + void add_armature_bones(Object *ob_arm, + ViewLayer *view_layer, + SceneExporter *se, + std::vector<Object *> &child_objects); + + bool add_instance_controller(Object *ob); + + private: + BlenderContext &blender_context; + BCExportSettings &export_settings; + +#if 0 + std::vector<Object *> written_armatures; + + bool already_written(Object *ob_arm); + + void wrote(Object *ob_arm); + + void find_objects_using_armature(Object *ob_arm, std::vector<Object *> &objects, Scene *sce); +#endif + + // Scene, SceneExporter and the list of child_objects + // are required for writing bone parented objects + void add_bone_node(Bone *bone, + Object *ob_arm, + SceneExporter *se, + std::vector<Object *> &child_objects); + + inline bool can_export(Bone *bone) + { + return !(export_settings.get_deform_bones_only() && bone->flag & BONE_NO_DEFORM); + } + + bool is_export_root(Bone *bone); + void add_bone_transform(Object *ob_arm, Bone *bone, COLLADASW::Node &node); + + std::string get_controller_id(Object *ob_arm, Object *ob); + + void write_bone_URLs(COLLADASW::InstanceController &ins, Object *ob_arm, Bone *bone); +}; + +#endif diff --git a/source/blender/io/collada/ArmatureImporter.cpp b/source/blender/io/collada/ArmatureImporter.cpp new file mode 100644 index 00000000000..b8c534f97dd --- /dev/null +++ b/source/blender/io/collada/ArmatureImporter.cpp @@ -0,0 +1,1115 @@ +/* + * 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 collada + */ + +/* COLLADABU_ASSERT, may be able to remove later */ +#include "COLLADABUPlatform.h" + +#include <algorithm> + +#include "COLLADAFWUniqueId.h" + +extern "C" { +#include "BKE_action.h" +#include "BKE_object.h" +#include "BKE_armature.h" +#include "BLI_string.h" +#include "BLI_listbase.h" +#include "ED_armature.h" +} + +#include "DEG_depsgraph.h" + +#include "collada_utils.h" +#include "ArmatureImporter.h" + +/* use node name, or fall back to original id if not present (name is optional) */ +template<class T> static const char *bc_get_joint_name(T *node) +{ + const std::string &id = node->getName(); + return id.size() ? id.c_str() : node->getOriginalId().c_str(); +} + +ArmatureImporter::ArmatureImporter(UnitConverter *conv, + MeshImporterBase *mesh, + Main *bmain, + Scene *sce, + ViewLayer *view_layer, + const ImportSettings *import_settings) + : TransformReader(conv), + m_bmain(bmain), + scene(sce), + view_layer(view_layer), + unit_converter(conv), + import_settings(import_settings), + empty(NULL), + mesh_importer(mesh) +{ +} + +ArmatureImporter::~ArmatureImporter() +{ + /* free skin controller data if we forget to do this earlier */ + std::map<COLLADAFW::UniqueId, SkinInfo>::iterator it; + for (it = skin_by_data_uid.begin(); it != skin_by_data_uid.end(); it++) { + it->second.free(); + } +} + +#if 0 +JointData *ArmatureImporter::get_joint_data(COLLADAFW::Node *node); +{ + const COLLADAFW::UniqueId &joint_id = node->getUniqueId(); + + if (joint_id_to_joint_index_map.find(joint_id) == joint_id_to_joint_index_map.end()) { + fprintf( + stderr, "Cannot find a joint index by joint id for %s.\n", node->getOriginalId().c_str()); + return NULL; + } + + int joint_index = joint_id_to_joint_index_map[joint_id]; + + return &joint_index_to_joint_info_map[joint_index]; +} +#endif + +int ArmatureImporter::create_bone(SkinInfo *skin, + COLLADAFW::Node *node, + EditBone *parent, + int totchild, + float parent_mat[4][4], + bArmature *arm, + std::vector<std::string> &layer_labels) +{ + float mat[4][4]; + float joint_inv_bind_mat[4][4]; + float joint_bind_mat[4][4]; + int chain_length = 0; + + /* Checking if bone is already made. */ + std::vector<COLLADAFW::Node *>::iterator it; + it = std::find(finished_joints.begin(), finished_joints.end(), node); + if (it != finished_joints.end()) { + return chain_length; + } + + EditBone *bone = ED_armature_ebone_add(arm, bc_get_joint_name(node)); + totbone++; + + /* + * We use the inv_bind_shape matrix to apply the armature bind pose as its rest pose. + */ + + std::map<COLLADAFW::UniqueId, SkinInfo>::iterator skin_it; + bool bone_is_skinned = false; + for (skin_it = skin_by_data_uid.begin(); skin_it != skin_by_data_uid.end(); skin_it++) { + + SkinInfo *b = &skin_it->second; + if (b->get_joint_inv_bind_matrix(joint_inv_bind_mat, node)) { + + /* get original world-space matrix */ + invert_m4_m4(mat, joint_inv_bind_mat); + copy_m4_m4(joint_bind_mat, mat); + /* And make local to armature */ + Object *ob_arm = skin->BKE_armature_from_object(); + if (ob_arm) { + float invmat[4][4]; + invert_m4_m4(invmat, ob_arm->obmat); + mul_m4_m4m4(mat, invmat, mat); + } + + bone_is_skinned = true; + break; + } + } + + /* create a bone even if there's no joint data for it (i.e. it has no influence) */ + if (!bone_is_skinned) { + get_node_mat(mat, node, NULL, NULL, parent_mat); + } + + if (parent) { + bone->parent = parent; + } + + float loc[3], size[3], rot[3][3]; + BoneExtensionMap &extended_bones = bone_extension_manager.getExtensionMap(arm); + BoneExtended &be = add_bone_extended(bone, node, totchild, layer_labels, extended_bones); + int layer = be.get_bone_layers(); + if (layer) { + bone->layer = layer; + } + arm->layer |= layer; // ensure that all populated bone layers are visible after import + + float *tail = be.get_tail(); + int use_connect = be.get_use_connect(); + + switch (use_connect) { + case 1: + bone->flag |= BONE_CONNECTED; + break; + case -1: /* Connect type not specified */ + case 0: + bone->flag &= ~BONE_CONNECTED; + break; + } + + if (be.has_roll()) { + bone->roll = be.get_roll(); + } + else { + float angle; + mat4_to_loc_rot_size(loc, rot, size, mat); + mat3_to_vec_roll(rot, NULL, &angle); + bone->roll = angle; + } + copy_v3_v3(bone->head, mat[3]); + + if (bone_is_skinned && this->import_settings->keep_bind_info) { + float rest_mat[4][4]; + get_node_mat(rest_mat, node, NULL, NULL, NULL); + bc_set_IDPropertyMatrix(bone, "bind_mat", joint_bind_mat); + bc_set_IDPropertyMatrix(bone, "rest_mat", rest_mat); + } + + add_v3_v3v3(bone->tail, bone->head, tail); /* tail must be non zero */ + + /* find smallest bone length in armature (used later for leaf bone length) */ + if (parent) { + + if (use_connect == 1) { + copy_v3_v3(parent->tail, bone->head); + } + + /* guess reasonable leaf bone length */ + float length = len_v3v3(parent->head, bone->head); + if ((length < leaf_bone_length || totbone == 0) && length > MINIMUM_BONE_LENGTH) { + leaf_bone_length = length; + } + } + + COLLADAFW::NodePointerArray &children = node->getChildNodes(); + + for (unsigned int i = 0; i < children.getCount(); i++) { + int cl = create_bone(skin, children[i], bone, children.getCount(), mat, arm, layer_labels); + if (cl > chain_length) { + chain_length = cl; + } + } + + bone->length = len_v3v3(bone->head, bone->tail); + joint_by_uid[node->getUniqueId()] = node; + finished_joints.push_back(node); + + be.set_chain_length(chain_length + 1); + + return chain_length + 1; +} + +/** + * Collada only knows Joints, hence bones at the end of a bone chain + * don't have a defined length. This function guesses reasonable + * tail locations for the affected bones (nodes which don't have any connected child) + * Hint: The extended_bones set gets populated in ArmatureImporter::create_bone + */ +void ArmatureImporter::fix_leaf_bone_hierarchy(bArmature *armature, + Bone *bone, + bool fix_orientation) +{ + if (bone == NULL) { + return; + } + + if (bc_is_leaf_bone(bone)) { + BoneExtensionMap &extended_bones = bone_extension_manager.getExtensionMap(armature); + BoneExtended *be = extended_bones[bone->name]; + EditBone *ebone = bc_get_edit_bone(armature, bone->name); + fix_leaf_bone(armature, ebone, be, fix_orientation); + } + + for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) { + fix_leaf_bone_hierarchy(armature, child, fix_orientation); + } +} + +void ArmatureImporter::fix_leaf_bone(bArmature *armature, + EditBone *ebone, + BoneExtended *be, + bool fix_orientation) +{ + if (be == NULL || !be->has_tail()) { + + /* Collada only knows Joints, Here we guess a reasonable leaf bone length */ + float leaf_length = (leaf_bone_length == FLT_MAX) ? 1.0 : leaf_bone_length; + + float vec[3]; + + if (fix_orientation && ebone->parent != NULL) { + EditBone *parent = ebone->parent; + sub_v3_v3v3(vec, ebone->head, parent->head); + if (len_squared_v3(vec) < MINIMUM_BONE_LENGTH) { + sub_v3_v3v3(vec, parent->tail, parent->head); + } + } + else { + vec[2] = 0.1f; + sub_v3_v3v3(vec, ebone->tail, ebone->head); + } + + normalize_v3_v3(vec, vec); + mul_v3_fl(vec, leaf_length); + add_v3_v3v3(ebone->tail, ebone->head, vec); + } +} + +void ArmatureImporter::fix_parent_connect(bArmature *armature, Bone *bone) +{ + /* armature has no bones */ + if (bone == NULL) { + return; + } + + if (bone->parent && bone->flag & BONE_CONNECTED) { + copy_v3_v3(bone->parent->tail, bone->head); + } + + for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) { + fix_parent_connect(armature, child); + } +} + +void ArmatureImporter::connect_bone_chains(bArmature *armature, Bone *parentbone, int clip) +{ + BoneExtensionMap &extended_bones = bone_extension_manager.getExtensionMap(armature); + BoneExtended *dominant_child = NULL; + int maxlen = 0; + + if (parentbone == NULL) { + return; + } + + Bone *child = (Bone *)parentbone->childbase.first; + if (child && (import_settings->find_chains || child->next == NULL)) { + for (; child; child = child->next) { + BoneExtended *be = extended_bones[child->name]; + if (be != NULL) { + int chain_len = be->get_chain_length(); + if (chain_len <= clip) { + if (chain_len > maxlen) { + dominant_child = be; + maxlen = chain_len; + } + else if (chain_len == maxlen) { + dominant_child = NULL; + } + } + } + } + } + + BoneExtended *pbe = extended_bones[parentbone->name]; + if (dominant_child != NULL) { + /* Found a valid chain. Now connect current bone with that chain.*/ + EditBone *pebone = bc_get_edit_bone(armature, parentbone->name); + EditBone *cebone = bc_get_edit_bone(armature, dominant_child->get_name()); + if (pebone && !(cebone->flag & BONE_CONNECTED)) { + float vec[3]; + sub_v3_v3v3(vec, cebone->head, pebone->head); + + /* + * It is possible that the child's head is located on the parents head. + * When this happens, then moving the parent's tail to the child's head + * would result in a zero sized bone and Blender would silently remove the bone. + * So we move the tail only when the resulting bone has a minimum length: + */ + + if (len_squared_v3(vec) > MINIMUM_BONE_LENGTH) { + copy_v3_v3(pebone->tail, cebone->head); + pbe->set_tail(pebone->tail); /* to make fix_leafbone happy ...*/ + if (pbe && pbe->get_chain_length() >= this->import_settings->min_chain_length) { + + BoneExtended *cbe = extended_bones[cebone->name]; + cbe->set_use_connect(true); + + cebone->flag |= BONE_CONNECTED; + pbe->set_leaf_bone(false); + printf("Connect Bone chain: parent (%s --> %s) child)\n", pebone->name, cebone->name); + } + } + } + for (Bone *ch = (Bone *)parentbone->childbase.first; ch; ch = ch->next) { + ArmatureImporter::connect_bone_chains(armature, ch, UNLIMITED_CHAIN_MAX); + } + } + else if (maxlen > 1 && maxlen > this->import_settings->min_chain_length) { + /* Try again with smaller chain length */ + ArmatureImporter::connect_bone_chains(armature, parentbone, maxlen - 1); + } + else { + /* can't connect this Bone. Proceed with children ... */ + if (pbe) { + pbe->set_leaf_bone(true); + } + for (Bone *ch = (Bone *)parentbone->childbase.first; ch; ch = ch->next) { + ArmatureImporter::connect_bone_chains(armature, ch, UNLIMITED_CHAIN_MAX); + } + } +} + +#if 0 +void ArmatureImporter::set_leaf_bone_shapes(Object *ob_arm) +{ + bPose *pose = ob_arm->pose; + + std::vector<LeafBone>::iterator it; + for (it = leaf_bones.begin(); it != leaf_bones.end(); it++) { + LeafBone &leaf = *it; + + bPoseChannel *pchan = BKE_pose_channel_find_name(pose, leaf.name); + if (pchan) { + pchan->custom = get_empty_for_leaves(); + } + else { + fprintf(stderr, "Cannot find a pose channel for leaf bone %s\n", leaf.name); + } + } +} + +void ArmatureImporter::set_euler_rotmode() +{ + /* just set rotmode = ROT_MODE_EUL on pose channel for each joint */ + + std::map<COLLADAFW::UniqueId, COLLADAFW::Node *>::iterator it; + + for (it = joint_by_uid.begin(); it != joint_by_uid.end(); it++) { + + COLLADAFW::Node *joint = it->second; + + std::map<COLLADAFW::UniqueId, SkinInfo>::iterator sit; + + for (sit = skin_by_data_uid.begin(); sit != skin_by_data_uid.end(); sit++) { + SkinInfo &skin = sit->second; + + if (skin.uses_joint_or_descendant(joint)) { + bPoseChannel *pchan = skin.get_pose_channel_from_node(joint); + + if (pchan) { + pchan->rotmode = ROT_MODE_EUL; + } + else { + fprintf(stderr, "Cannot find pose channel for %s.\n", get_joint_name(joint)); + } + + break; + } + } + } +} +#endif + +Object *ArmatureImporter::get_empty_for_leaves() +{ + if (empty) { + return empty; + } + + empty = bc_add_object(m_bmain, scene, view_layer, OB_EMPTY, NULL); + empty->empty_drawtype = OB_EMPTY_SPHERE; + + return empty; +} + +#if 0 +Object *ArmatureImporter::find_armature(COLLADAFW::Node *node) +{ + JointData *jd = get_joint_data(node); + if (jd) { + return jd->ob_arm; + } + + COLLADAFW::NodePointerArray &children = node->getChildNodes(); + for (int i = 0; i < children.getCount(); i++) { + Object *ob_arm = find_armature(children[i]); + if (ob_arm) { + return ob_arm; + } + } + + return NULL; +} + +ArmatureJoints &ArmatureImporter::get_armature_joints(Object *ob_arm) +{ + /* try finding it */ + std::vector<ArmatureJoints>::iterator it; + for (it = armature_joints.begin(); it != armature_joints.end(); it++) { + if ((*it).ob_arm == ob_arm) { + return *it; + } + } + + /* not found, create one */ + ArmatureJoints aj; + aj.ob_arm = ob_arm; + armature_joints.push_back(aj); + + return armature_joints.back(); +} +#endif +void ArmatureImporter::create_armature_bones(Main *bmain, std::vector<Object *> &ob_arms) +{ + std::vector<COLLADAFW::Node *>::iterator ri; + std::vector<std::string> layer_labels; + + /* if there is an armature created for root_joint next root_joint */ + for (ri = root_joints.begin(); ri != root_joints.end(); ri++) { + COLLADAFW::Node *node = *ri; + if (get_armature_for_joint(node) != NULL) { + continue; + } + + Object *ob_arm = joint_parent_map[node->getUniqueId()]; + if (!ob_arm) { + continue; + } + + bArmature *armature = (bArmature *)ob_arm->data; + if (!armature) { + continue; + } + + char *bone_name = (char *)bc_get_joint_name(node); + Bone *bone = BKE_armature_find_bone_name(armature, bone_name); + if (bone) { + fprintf(stderr, + "Reuse of child bone [%s] as root bone in same Armature is not supported.\n", + bone_name); + continue; + } + + ED_armature_to_edit(armature); + armature->layer = 0; // layer is set according to imported bone set in create_bone() + + create_bone(NULL, node, NULL, node->getChildNodes().getCount(), NULL, armature, layer_labels); + if (this->import_settings->find_chains) { + connect_bone_chains(armature, (Bone *)armature->bonebase.first, UNLIMITED_CHAIN_MAX); + } + + /* exit armature edit mode to populate the Armature object */ + ED_armature_from_edit(bmain, armature); + ED_armature_edit_free(armature); + ED_armature_to_edit(armature); + + fix_leaf_bone_hierarchy( + armature, (Bone *)armature->bonebase.first, this->import_settings->fix_orientation); + unskinned_armature_map[node->getUniqueId()] = ob_arm; + + ED_armature_from_edit(bmain, armature); + ED_armature_edit_free(armature); + + set_bone_transformation_type(node, ob_arm); + + int index = std::find(ob_arms.begin(), ob_arms.end(), ob_arm) - ob_arms.begin(); + if (index == 0) { + ob_arms.push_back(ob_arm); + } + + DEG_id_tag_update(&ob_arm->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + } +} + +Object *ArmatureImporter::create_armature_bones(Main *bmain, SkinInfo &skin) +{ + /* just do like so: + * - get armature + * - enter editmode + * - add edit bones and head/tail properties using matrices and parent-child info + * - exit edit mode + * - set a sphere shape to leaf bones */ + + Object *ob_arm = NULL; + + /* + * find if there's another skin sharing at least one bone with this skin + * if so, use that skin's armature + */ + + /** + * Pseudocode: + * + * find_node_in_tree(node, root_joint) + * + * skin::find_root_joints(root_joints): + * std::vector root_joints; + * for each root in root_joints: + * for each joint in joints: + * if find_node_in_tree(joint, root): + * if (std::find(root_joints.begin(), root_joints.end(), root) == + * root_joints.end()) root_joints.push_back(root); + * + * for (each skin B with armature) { + * find all root joints for skin B + * + * for each joint X in skin A: + * for each root joint R in skin B: + * if (find_node_in_tree(X, R)) { + * shared = 1; + * goto endloop; + * } + * } + * + * endloop: + */ + + SkinInfo *a = &skin; + Object *shared = NULL; + std::vector<COLLADAFW::Node *> skin_root_joints; + std::vector<std::string> layer_labels; + + std::map<COLLADAFW::UniqueId, SkinInfo>::iterator it; + for (it = skin_by_data_uid.begin(); it != skin_by_data_uid.end(); it++) { + SkinInfo *b = &it->second; + if (b == a || b->BKE_armature_from_object() == NULL) { + continue; + } + + skin_root_joints.clear(); + + b->find_root_joints(root_joints, joint_by_uid, skin_root_joints); + + std::vector<COLLADAFW::Node *>::iterator ri; + for (ri = skin_root_joints.begin(); ri != skin_root_joints.end(); ri++) { + COLLADAFW::Node *node = *ri; + if (a->uses_joint_or_descendant(node)) { + shared = b->BKE_armature_from_object(); + break; + } + } + + if (shared != NULL) { + break; + } + } + + if (!shared && this->joint_parent_map.size() > 0) { + /* All armatures have been created while creating the Node tree. + * The Collada exporter currently does not create a + * strict relationship between geometries and armatures + * So when we reimport a Blender collada file, then we have + * to guess what is meant. + * XXX This is not safe when we have more than one armatures + * in the import. */ + shared = this->joint_parent_map.begin()->second; + } + + if (shared) { + ob_arm = skin.set_armature(shared); + } + else { + ob_arm = skin.create_armature(m_bmain, scene, view_layer); // once for every armature + } + + /* enter armature edit mode */ + bArmature *armature = (bArmature *)ob_arm->data; + ED_armature_to_edit(armature); + + totbone = 0; + // bone_direction_row = 1; // TODO: don't default to Y but use asset and based on it decide on + /* default row */ + + /* create bones */ + /* TODO: + * check if bones have already been created for a given joint */ + + std::vector<COLLADAFW::Node *>::iterator ri; + for (ri = root_joints.begin(); ri != root_joints.end(); ri++) { + COLLADAFW::Node *node = *ri; + /* for shared armature check if bone tree is already created */ + if (shared && std::find(skin_root_joints.begin(), skin_root_joints.end(), node) != + skin_root_joints.end()) { + continue; + } + + /* since root_joints may contain joints for multiple controllers, we need to filter */ + if (skin.uses_joint_or_descendant(node)) { + + create_bone( + &skin, node, NULL, node->getChildNodes().getCount(), NULL, armature, layer_labels); + + if (joint_parent_map.find(node->getUniqueId()) != joint_parent_map.end() && + !skin.get_parent()) { + skin.set_parent(joint_parent_map[node->getUniqueId()]); + } + } + } + + /* exit armature edit mode to populate the Armature object */ + ED_armature_from_edit(bmain, armature); + ED_armature_edit_free(armature); + + for (ri = root_joints.begin(); ri != root_joints.end(); ri++) { + COLLADAFW::Node *node = *ri; + set_bone_transformation_type(node, ob_arm); + } + + ED_armature_to_edit(armature); + if (this->import_settings->find_chains) { + connect_bone_chains(armature, (Bone *)armature->bonebase.first, UNLIMITED_CHAIN_MAX); + } + fix_leaf_bone_hierarchy( + armature, (Bone *)armature->bonebase.first, this->import_settings->fix_orientation); + ED_armature_from_edit(bmain, armature); + ED_armature_edit_free(armature); + + DEG_id_tag_update(&ob_arm->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + + return ob_arm; +} + +void ArmatureImporter::set_bone_transformation_type(const COLLADAFW::Node *node, Object *ob_arm) +{ + bPoseChannel *pchan = BKE_pose_channel_find_name(ob_arm->pose, bc_get_joint_name(node)); + if (pchan) { + pchan->rotmode = (node_is_decomposed(node)) ? ROT_MODE_EUL : ROT_MODE_QUAT; + } + + COLLADAFW::NodePointerArray childnodes = node->getChildNodes(); + for (int index = 0; index < childnodes.getCount(); index++) { + node = childnodes[index]; + set_bone_transformation_type(node, ob_arm); + } +} + +void ArmatureImporter::set_pose(Object *ob_arm, + COLLADAFW::Node *root_node, + const char *parentname, + float parent_mat[4][4]) +{ + const char *bone_name = bc_get_joint_name(root_node); + float mat[4][4]; + float obmat[4][4]; + + /* object-space */ + get_node_mat(obmat, root_node, NULL, NULL); + bool is_decomposed = node_is_decomposed(root_node); + + // if (*edbone) + bPoseChannel *pchan = BKE_pose_channel_find_name(ob_arm->pose, bone_name); + pchan->rotmode = (is_decomposed) ? ROT_MODE_EUL : ROT_MODE_QUAT; + + // else fprintf ( "", + + /* get world-space */ + if (parentname) { + mul_m4_m4m4(mat, parent_mat, obmat); + bPoseChannel *parchan = BKE_pose_channel_find_name(ob_arm->pose, parentname); + + mul_m4_m4m4(pchan->pose_mat, parchan->pose_mat, mat); + } + else { + + copy_m4_m4(mat, obmat); + float invObmat[4][4]; + invert_m4_m4(invObmat, ob_arm->obmat); + mul_m4_m4m4(pchan->pose_mat, invObmat, mat); + } + +#if 0 + float angle = 0.0f; + mat4_to_axis_angle(ax, &angle, mat); + pchan->bone->roll = angle; +#endif + + COLLADAFW::NodePointerArray &children = root_node->getChildNodes(); + for (unsigned int i = 0; i < children.getCount(); i++) { + set_pose(ob_arm, children[i], bone_name, mat); + } +} + +bool ArmatureImporter::node_is_decomposed(const COLLADAFW::Node *node) +{ + const COLLADAFW::TransformationPointerArray &nodeTransforms = node->getTransformations(); + for (unsigned int i = 0; i < nodeTransforms.getCount(); i++) { + COLLADAFW::Transformation *transform = nodeTransforms[i]; + COLLADAFW::Transformation::TransformationType tm_type = transform->getTransformationType(); + if (tm_type == COLLADAFW::Transformation::MATRIX) { + return false; + } + } + return true; +} + +/** + * root - if this joint is the top joint in hierarchy, if a joint + * is a child of a node (not joint), root should be true since + * this is where we build armature bones from + */ +void ArmatureImporter::add_root_joint(COLLADAFW::Node *node, Object *parent) +{ + root_joints.push_back(node); + if (parent) { + joint_parent_map[node->getUniqueId()] = parent; + } +} + +#if 0 +void ArmatureImporter::add_root_joint(COLLADAFW::Node *node) +{ + // root_joints.push_back(node); + Object *ob_arm = find_armature(node); + if (ob_arm) { + get_armature_joints(ob_arm).root_joints.push_back(node); + } +# ifdef COLLADA_DEBUG + else { + fprintf(stderr, "%s cannot be added to armature.\n", get_joint_name(node)); + } +# endif +} +#endif + +/* here we add bones to armatures, having armatures previously created in write_controller */ +void ArmatureImporter::make_armatures(bContext *C, std::vector<Object *> &objects_to_scale) +{ + Main *bmain = CTX_data_main(C); + std::vector<Object *> ob_arms; + std::map<COLLADAFW::UniqueId, SkinInfo>::iterator it; + + /* TODO: Make this work for more than one armature in the import file. */ + leaf_bone_length = FLT_MAX; + + for (it = skin_by_data_uid.begin(); it != skin_by_data_uid.end(); it++) { + + SkinInfo &skin = it->second; + + Object *ob_arm = create_armature_bones(bmain, skin); + + /* link armature with a mesh object */ + const COLLADAFW::UniqueId &uid = skin.get_controller_uid(); + const COLLADAFW::UniqueId *guid = get_geometry_uid(uid); + if (guid != NULL) { + Object *ob = mesh_importer->get_object_by_geom_uid(*guid); + if (ob) { + skin.link_armature(C, ob, joint_by_uid, this); + + std::vector<Object *>::iterator ob_it = std::find( + objects_to_scale.begin(), objects_to_scale.end(), ob); + + if (ob_it != objects_to_scale.end()) { + int index = ob_it - objects_to_scale.begin(); + objects_to_scale.erase(objects_to_scale.begin() + index); + } + + if (std::find(objects_to_scale.begin(), objects_to_scale.end(), ob_arm) == + objects_to_scale.end()) { + objects_to_scale.push_back(ob_arm); + } + + if (std::find(ob_arms.begin(), ob_arms.end(), ob_arm) == ob_arms.end()) { + ob_arms.push_back(ob_arm); + } + } + else { + fprintf(stderr, "Cannot find object to link armature with.\n"); + } + } + else { + fprintf(stderr, "Cannot find geometry to link armature with.\n"); + } + + /* set armature parent if any */ + Object *par = skin.get_parent(); + if (par) { + bc_set_parent(skin.BKE_armature_from_object(), par, C, false); + } + + /* free memory stolen from SkinControllerData */ + skin.free(); + } + + /* for bones without skins */ + create_armature_bones(bmain, ob_arms); + + /* Fix bone relations */ + std::vector<Object *>::iterator ob_arm_it; + for (ob_arm_it = ob_arms.begin(); ob_arm_it != ob_arms.end(); ob_arm_it++) { + + Object *ob_arm = *ob_arm_it; + bArmature *armature = (bArmature *)ob_arm->data; + + /* and step back to edit mode to fix the leaf nodes */ + ED_armature_to_edit(armature); + + fix_parent_connect(armature, (Bone *)armature->bonebase.first); + + ED_armature_from_edit(bmain, armature); + ED_armature_edit_free(armature); + } +} + +#if 0 +/* link with meshes, create vertex groups, assign weights */ +void ArmatureImporter::link_armature(Object *ob_arm, + const COLLADAFW::UniqueId &geom_id, + const COLLADAFW::UniqueId &controller_data_id) +{ + Object *ob = mesh_importer->get_object_by_geom_uid(geom_id); + + if (!ob) { + fprintf(stderr, "Cannot find object by geometry UID.\n"); + return; + } + + if (skin_by_data_uid.find(controller_data_id) == skin_by_data_uid.end()) { + fprintf(stderr, "Cannot find skin info by controller data UID.\n"); + return; + } + + SkinInfo &skin = skin_by_data_uid[conroller_data_id]; + + /* create vertex groups */ +} +#endif + +bool ArmatureImporter::write_skin_controller_data(const COLLADAFW::SkinControllerData *data) +{ + /* at this stage we get vertex influence info that should go into me->verts and ob->defbase + * there's no info to which object this should be long so we associate it with + * skin controller data UID. */ + + /* don't forget to call BKE_object_defgroup_unique_name before we copy */ + + /* controller data uid -> [armature] -> joint data, + * [mesh object] */ + + SkinInfo skin(unit_converter); + skin.borrow_skin_controller_data(data); + + /* store join inv bind matrix to use it later in armature construction */ + const COLLADAFW::Matrix4Array &inv_bind_mats = data->getInverseBindMatrices(); + for (unsigned int i = 0; i < data->getJointsCount(); i++) { + skin.add_joint(inv_bind_mats[i]); + } + + skin_by_data_uid[data->getUniqueId()] = skin; + + return true; +} + +bool ArmatureImporter::write_controller(const COLLADAFW::Controller *controller) +{ + /* - create and store armature object */ + const COLLADAFW::UniqueId &con_id = controller->getUniqueId(); + + if (controller->getControllerType() == COLLADAFW::Controller::CONTROLLER_TYPE_SKIN) { + COLLADAFW::SkinController *co = (COLLADAFW::SkinController *)controller; + /* to be able to find geom id by controller id */ + geom_uid_by_controller_uid[con_id] = co->getSource(); + + const COLLADAFW::UniqueId &data_uid = co->getSkinControllerData(); + if (skin_by_data_uid.find(data_uid) == skin_by_data_uid.end()) { + fprintf(stderr, "Cannot find skin by controller data UID.\n"); + return true; + } + + skin_by_data_uid[data_uid].set_controller(co); + } + /* morph controller */ + else if (controller->getControllerType() == COLLADAFW::Controller::CONTROLLER_TYPE_MORPH) { + COLLADAFW::MorphController *co = (COLLADAFW::MorphController *)controller; + /* to be able to find geom id by controller id */ + geom_uid_by_controller_uid[con_id] = co->getSource(); + /* Shape keys are applied in DocumentImporter->finish() */ + morph_controllers.push_back(co); + } + + return true; +} + +void ArmatureImporter::make_shape_keys(bContext *C) +{ + Main *bmain = CTX_data_main(C); + std::vector<COLLADAFW::MorphController *>::iterator mc; + float weight; + + for (mc = morph_controllers.begin(); mc != morph_controllers.end(); mc++) { + /* Controller data */ + COLLADAFW::UniqueIdArray &morphTargetIds = (*mc)->getMorphTargets(); + COLLADAFW::FloatOrDoubleArray &morphWeights = (*mc)->getMorphWeights(); + + /* Prereq: all the geometries must be imported and mesh objects must be made */ + Object *source_ob = this->mesh_importer->get_object_by_geom_uid((*mc)->getSource()); + + if (source_ob) { + + Mesh *source_me = (Mesh *)source_ob->data; + /* insert key to source mesh */ + Key *key = source_me->key = BKE_key_add(bmain, (ID *)source_me); + key->type = KEY_RELATIVE; + KeyBlock *kb; + + /* insert basis key */ + kb = BKE_keyblock_add_ctime(key, "Basis", false); + BKE_keyblock_convert_from_mesh(source_me, key, kb); + + /* insert other shape keys */ + for (int i = 0; i < morphTargetIds.getCount(); i++) { + /* better to have a separate map of morph objects, + * This'll do for now since only mesh morphing is imported */ + + Mesh *me = this->mesh_importer->get_mesh_by_geom_uid(morphTargetIds[i]); + + if (me) { + me->key = key; + std::string morph_name = *this->mesh_importer->get_geometry_name(me->id.name); + + kb = BKE_keyblock_add_ctime(key, morph_name.c_str(), false); + BKE_keyblock_convert_from_mesh(me, key, kb); + + /* apply weights */ + weight = morphWeights.getFloatValues()->getData()[i]; + kb->curval = weight; + } + else { + fprintf(stderr, "Morph target geometry not found.\n"); + } + } + } + else { + fprintf(stderr, "Morph target object not found.\n"); + } + } +} + +COLLADAFW::UniqueId *ArmatureImporter::get_geometry_uid(const COLLADAFW::UniqueId &controller_uid) +{ + if (geom_uid_by_controller_uid.find(controller_uid) == geom_uid_by_controller_uid.end()) { + return NULL; + } + + return &geom_uid_by_controller_uid[controller_uid]; +} + +Object *ArmatureImporter::get_armature_for_joint(COLLADAFW::Node *node) +{ + std::map<COLLADAFW::UniqueId, SkinInfo>::iterator it; + for (it = skin_by_data_uid.begin(); it != skin_by_data_uid.end(); it++) { + SkinInfo &skin = it->second; + + if (skin.uses_joint_or_descendant(node)) { + return skin.BKE_armature_from_object(); + } + } + + std::map<COLLADAFW::UniqueId, Object *>::iterator arm; + for (arm = unskinned_armature_map.begin(); arm != unskinned_armature_map.end(); arm++) { + if (arm->first == node->getUniqueId()) { + return arm->second; + } + } + return NULL; +} + +void ArmatureImporter::set_tags_map(TagsMap &tagsMap) +{ + this->uid_tags_map = tagsMap; +} + +void ArmatureImporter::get_rna_path_for_joint(COLLADAFW::Node *node, + char *joint_path, + size_t count) +{ + BLI_snprintf(joint_path, count, "pose.bones[\"%s\"]", bc_get_joint_name(node)); +} + +/* gives a world-space mat */ +bool ArmatureImporter::get_joint_bind_mat(float m[4][4], COLLADAFW::Node *joint) +{ + std::map<COLLADAFW::UniqueId, SkinInfo>::iterator it; + bool found = false; + for (it = skin_by_data_uid.begin(); it != skin_by_data_uid.end(); it++) { + SkinInfo &skin = it->second; + if ((found = skin.get_joint_inv_bind_matrix(m, joint))) { + invert_m4(m); + break; + } + } + + return found; +} + +BoneExtended &ArmatureImporter::add_bone_extended(EditBone *bone, + COLLADAFW::Node *node, + int sibcount, + std::vector<std::string> &layer_labels, + BoneExtensionMap &extended_bones) +{ + BoneExtended *be = new BoneExtended(bone); + extended_bones[bone->name] = be; + + TagsMap::iterator etit; + ExtraTags *et = 0; + etit = uid_tags_map.find(node->getUniqueId().toAscii()); + + bool has_connect = false; + int connect_type = -1; + + if (etit != uid_tags_map.end()) { + + float tail[3] = {FLT_MAX, FLT_MAX, FLT_MAX}; + float roll = 0; + std::string layers; + + et = etit->second; + + bool has_tail = false; + has_tail |= et->setData("tip_x", &tail[0]); + has_tail |= et->setData("tip_y", &tail[1]); + has_tail |= et->setData("tip_z", &tail[2]); + + has_connect = et->setData("connect", &connect_type); + bool has_roll = et->setData("roll", &roll); + + layers = et->setData("layer", layers); + + if (has_tail && !has_connect) { + /* got a bone tail definition but no connect info -> bone is not connected */ + has_connect = true; + connect_type = 0; + } + + be->set_bone_layers(layers, layer_labels); + if (has_tail) { + be->set_tail(tail); + } + if (has_roll) { + be->set_roll(roll); + } + } + + if (!has_connect && this->import_settings->auto_connect) { + /* auto connect only whyen parent has exactly one child*/ + connect_type = sibcount == 1; + } + + be->set_use_connect(connect_type); + be->set_leaf_bone(true); + + return *be; +} diff --git a/source/blender/io/collada/ArmatureImporter.h b/source/blender/io/collada/ArmatureImporter.h new file mode 100644 index 00000000000..da92c04e5dc --- /dev/null +++ b/source/blender/io/collada/ArmatureImporter.h @@ -0,0 +1,187 @@ +/* + * 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 collada + */ + +#ifndef __ARMATUREIMPORTER_H__ +#define __ARMATUREIMPORTER_H__ + +#include "COLLADAFWNode.h" +#include "COLLADAFWUniqueId.h" +#include "COLLADAFWMorphController.h" + +extern "C" { +#include "BKE_context.h" +#include "BKE_key.h" + +#include "DNA_armature_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_key_types.h" + +#include "ED_armature.h" +} + +#include "AnimationImporter.h" +#include "MeshImporter.h" +#include "SkinInfo.h" +#include "TransformReader.h" +#include "ExtraTags.h" + +#include <map> +#include <vector> + +#include "collada_internal.h" +#include "collada_utils.h" +#include "ImportSettings.h" + +#define UNLIMITED_CHAIN_MAX INT_MAX +#define MINIMUM_BONE_LENGTH 0.000001f + +class ArmatureImporter : private TransformReader { + private: + Main *m_bmain; + Scene *scene; + ViewLayer *view_layer; + UnitConverter *unit_converter; + const ImportSettings *import_settings; + + // std::map<int, JointData> joint_index_to_joint_info_map; + // std::map<COLLADAFW::UniqueId, int> joint_id_to_joint_index_map; + BoneExtensionManager bone_extension_manager; + // int bone_direction_row; // XXX not used + float leaf_bone_length; + int totbone; + // XXX not used + // float min_angle; // minimum angle between bone head-tail and a row of bone matrix + +#if 0 + struct ArmatureJoints { + Object *ob_arm; + std::vector<COLLADAFW::Node *> root_joints; + }; + std::vector<ArmatureJoints> armature_joints; +#endif + + Object *empty; // empty for leaf bones + + std::map<COLLADAFW::UniqueId, COLLADAFW::UniqueId> geom_uid_by_controller_uid; + std::map<COLLADAFW::UniqueId, COLLADAFW::Node *> joint_by_uid; // contains all joints + std::vector<COLLADAFW::Node *> root_joints; + std::vector<COLLADAFW::Node *> finished_joints; + std::vector<COLLADAFW::MorphController *> morph_controllers; + std::map<COLLADAFW::UniqueId, Object *> joint_parent_map; + std::map<COLLADAFW::UniqueId, Object *> unskinned_armature_map; + + MeshImporterBase *mesh_importer; + + // This is used to store data passed in write_controller_data. + // Arrays from COLLADAFW::SkinControllerData lose ownership, so do this class members + // so that arrays don't get freed until we free them explicitly. + + std::map<COLLADAFW::UniqueId, SkinInfo> skin_by_data_uid; // data UID = skin controller data UID +#if 0 + JointData *get_joint_data(COLLADAFW::Node *node); +#endif + + int create_bone(SkinInfo *skin, + COLLADAFW::Node *node, + EditBone *parent, + int totchild, + float parent_mat[4][4], + bArmature *arm, + std::vector<std::string> &layer_labels); + + BoneExtended &add_bone_extended(EditBone *bone, + COLLADAFW::Node *node, + int sibcount, + std::vector<std::string> &layer_labels, + BoneExtensionMap &extended_bones); + + void fix_leaf_bone_hierarchy(bArmature *armature, Bone *bone, bool fix_orientation); + void fix_leaf_bone(bArmature *armature, EditBone *ebone, BoneExtended *be, bool fix_orientation); + void fix_parent_connect(bArmature *armature, Bone *bone); + void connect_bone_chains(bArmature *armature, Bone *bone, const int max_chain_length); + + void set_pose(Object *ob_arm, + COLLADAFW::Node *root_node, + const char *parentname, + float parent_mat[4][4]); + + void set_bone_transformation_type(const COLLADAFW::Node *node, Object *ob_arm); + bool node_is_decomposed(const COLLADAFW::Node *node); +#if 0 + void set_leaf_bone_shapes(Object *ob_arm); + void set_euler_rotmode(); +#endif + + Object *get_empty_for_leaves(); + +#if 0 + Object *find_armature(COLLADAFW::Node *node); + + ArmatureJoints &get_armature_joints(Object *ob_arm); +#endif + + Object *create_armature_bones(Main *bmain, SkinInfo &skin); + void create_armature_bones(Main *bmain, std::vector<Object *> &arm_objs); + + /** TagsMap typedef for uid_tags_map. */ + typedef std::map<std::string, ExtraTags *> TagsMap; + TagsMap uid_tags_map; + + public: + ArmatureImporter(UnitConverter *conv, + MeshImporterBase *mesh, + Main *bmain, + Scene *sce, + ViewLayer *view_layer, + const ImportSettings *import_settings); + ~ArmatureImporter(); + + void add_root_joint(COLLADAFW::Node *node, Object *parent); + + // here we add bones to armatures, having armatures previously created in write_controller + void make_armatures(bContext *C, std::vector<Object *> &objects_to_scale); + + void make_shape_keys(bContext *C); + +#if 0 + // link with meshes, create vertex groups, assign weights + void link_armature(Object *ob_arm, + const COLLADAFW::UniqueId &geom_id, + const COLLADAFW::UniqueId &controller_data_id); +#endif + + bool write_skin_controller_data(const COLLADAFW::SkinControllerData *data); + + bool write_controller(const COLLADAFW::Controller *controller); + + COLLADAFW::UniqueId *get_geometry_uid(const COLLADAFW::UniqueId &controller_uid); + + Object *get_armature_for_joint(COLLADAFW::Node *node); + + void get_rna_path_for_joint(COLLADAFW::Node *node, char *joint_path, size_t count); + + // gives a world-space mat + bool get_joint_bind_mat(float m[4][4], COLLADAFW::Node *joint); + + void set_tags_map(TagsMap &tags_map); +}; + +#endif diff --git a/source/blender/io/collada/BCAnimationCurve.cpp b/source/blender/io/collada/BCAnimationCurve.cpp new file mode 100644 index 00000000000..36800d611d2 --- /dev/null +++ b/source/blender/io/collada/BCAnimationCurve.cpp @@ -0,0 +1,691 @@ +/* + * 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) 2008 Blender Foundation. + * All rights reserved. + */ + +#include "BCAnimationCurve.h" + +BCAnimationCurve::BCAnimationCurve() +{ + this->curve_key.set_object_type(BC_ANIMATION_TYPE_OBJECT); + this->fcurve = NULL; + this->curve_is_local_copy = false; +} + +BCAnimationCurve::BCAnimationCurve(const BCAnimationCurve &other) +{ + this->min = other.min; + this->max = other.max; + this->fcurve = other.fcurve; + this->curve_key = other.curve_key; + this->curve_is_local_copy = false; + this->id_ptr = other.id_ptr; + + /* The fcurve of the new instance is a copy and can be modified */ + + get_edit_fcurve(); +} + +BCAnimationCurve::BCAnimationCurve(BCCurveKey key, Object *ob, FCurve *fcu) +{ + this->min = 0; + this->max = 0; + this->curve_key = key; + this->fcurve = fcu; + this->curve_is_local_copy = false; + init_pointer_rna(ob); +} + +BCAnimationCurve::BCAnimationCurve(const BCCurveKey &key, Object *ob) +{ + this->curve_key = key; + this->fcurve = NULL; + this->curve_is_local_copy = false; + init_pointer_rna(ob); +} + +void BCAnimationCurve::init_pointer_rna(Object *ob) +{ + switch (this->curve_key.get_animation_type()) { + case BC_ANIMATION_TYPE_BONE: { + bArmature *arm = (bArmature *)ob->data; + RNA_id_pointer_create(&arm->id, &id_ptr); + } break; + case BC_ANIMATION_TYPE_OBJECT: { + RNA_id_pointer_create(&ob->id, &id_ptr); + } break; + case BC_ANIMATION_TYPE_MATERIAL: { + Material *ma = BKE_object_material_get(ob, curve_key.get_subindex() + 1); + RNA_id_pointer_create(&ma->id, &id_ptr); + } break; + case BC_ANIMATION_TYPE_CAMERA: { + Camera *camera = (Camera *)ob->data; + RNA_id_pointer_create(&camera->id, &id_ptr); + } break; + case BC_ANIMATION_TYPE_LIGHT: { + Light *lamp = (Light *)ob->data; + RNA_id_pointer_create(&lamp->id, &id_ptr); + } break; + default: + fprintf( + stderr, "BC_animation_curve_type %d not supported", this->curve_key.get_array_index()); + break; + } +} + +void BCAnimationCurve::delete_fcurve(FCurve *fcu) +{ + free_fcurve(fcu); +} + +FCurve *BCAnimationCurve::create_fcurve(int array_index, const char *rna_path) +{ + FCurve *fcu = (FCurve *)MEM_callocN(sizeof(FCurve), "FCurve"); + fcu->flag = (FCURVE_VISIBLE | FCURVE_AUTO_HANDLES | FCURVE_SELECTED); + fcu->rna_path = BLI_strdupn(rna_path, strlen(rna_path)); + fcu->array_index = array_index; + return fcu; +} + +void BCAnimationCurve::create_bezt(float frame, float output) +{ + FCurve *fcu = get_edit_fcurve(); + BezTriple bez; + memset(&bez, 0, sizeof(BezTriple)); + bez.vec[1][0] = frame; + bez.vec[1][1] = output; + bez.ipo = U.ipo_new; /* use default interpolation mode here... */ + bez.f1 = bez.f2 = bez.f3 = SELECT; + bez.h1 = bez.h2 = HD_AUTO; + insert_bezt_fcurve(fcu, &bez, INSERTKEY_NOFLAGS); + calchandles_fcurve(fcu); +} + +BCAnimationCurve::~BCAnimationCurve() +{ + if (curve_is_local_copy && fcurve) { + // fprintf(stderr, "removed fcurve %s\n", fcurve->rna_path); + delete_fcurve(fcurve); + this->fcurve = NULL; + } +} + +const bool BCAnimationCurve::is_of_animation_type(BC_animation_type type) const +{ + return curve_key.get_animation_type() == type; +} + +const std::string BCAnimationCurve::get_channel_target() const +{ + const std::string path = curve_key.get_path(); + + if (bc_startswith(path, "pose.bones")) { + return bc_string_after(path, "pose.bones"); + } + return bc_string_after(path, "."); +} + +const std::string BCAnimationCurve::get_channel_type() const +{ + const std::string channel = get_channel_target(); + return bc_string_after(channel, "."); +} + +const std::string BCAnimationCurve::get_channel_posebone() const +{ + const std::string channel = get_channel_target(); + std::string pose_bone_name = bc_string_before(channel, "."); + if (pose_bone_name == channel) { + pose_bone_name = ""; + } + else { + pose_bone_name = bc_string_after(pose_bone_name, "\"["); + pose_bone_name = bc_string_before(pose_bone_name, "]\""); + } + return pose_bone_name; +} + +const std::string BCAnimationCurve::get_animation_name(Object *ob) const +{ + std::string name; + + switch (curve_key.get_animation_type()) { + case BC_ANIMATION_TYPE_OBJECT: { + name = id_name(ob); + } break; + + case BC_ANIMATION_TYPE_BONE: { + if (fcurve == NULL || fcurve->rna_path == NULL) { + name = ""; + } + else { + const char *boneName = BLI_str_quoted_substrN(fcurve->rna_path, "pose.bones["); + name = (boneName) ? id_name(ob) + "_" + std::string(boneName) : ""; + } + } break; + + case BC_ANIMATION_TYPE_CAMERA: { + Camera *camera = (Camera *)ob->data; + name = id_name(ob) + "-" + id_name(camera) + "-camera"; + } break; + + case BC_ANIMATION_TYPE_LIGHT: { + Light *lamp = (Light *)ob->data; + name = id_name(ob) + "-" + id_name(lamp) + "-light"; + } break; + + case BC_ANIMATION_TYPE_MATERIAL: { + Material *ma = BKE_object_material_get(ob, this->curve_key.get_subindex() + 1); + name = id_name(ob) + "-" + id_name(ma) + "-material"; + } break; + + default: { + name = ""; + } + } + + return name; +} + +const int BCAnimationCurve::get_channel_index() const +{ + return curve_key.get_array_index(); +} + +const int BCAnimationCurve::get_subindex() const +{ + return curve_key.get_subindex(); +} + +const std::string BCAnimationCurve::get_rna_path() const +{ + return curve_key.get_path(); +} + +const int BCAnimationCurve::sample_count() const +{ + if (fcurve == NULL) { + return 0; + } + return fcurve->totvert; +} + +const int BCAnimationCurve::closest_index_above(const float sample_frame, const int start_at) const +{ + if (fcurve == NULL) { + return -1; + } + + const int cframe = fcurve->bezt[start_at].vec[1][0]; // inacurate! + + if (fabs(cframe - sample_frame) < 0.00001) { + return start_at; + } + return (fcurve->totvert > start_at + 1) ? start_at + 1 : start_at; +} + +const int BCAnimationCurve::closest_index_below(const float sample_frame) const +{ + if (fcurve == NULL) { + return -1; + } + + float lower_frame = sample_frame; + float upper_frame = sample_frame; + int lower_index = 0; + int upper_index = 0; + + for (int fcu_index = 0; fcu_index < fcurve->totvert; fcu_index++) { + upper_index = fcu_index; + + const int cframe = fcurve->bezt[fcu_index].vec[1][0]; // inacurate! + if (cframe <= sample_frame) { + lower_frame = cframe; + lower_index = fcu_index; + } + if (cframe >= sample_frame) { + upper_frame = cframe; + break; + } + } + + if (lower_index == upper_index) { + return lower_index; + } + + const float fraction = float(sample_frame - lower_frame) / (upper_frame - lower_frame); + return (fraction < 0.5) ? lower_index : upper_index; +} + +const int BCAnimationCurve::get_interpolation_type(float sample_frame) const +{ + const int index = closest_index_below(sample_frame); + if (index < 0) { + return BEZT_IPO_BEZ; + } + return fcurve->bezt[index].ipo; +} + +const FCurve *BCAnimationCurve::get_fcurve() const +{ + return fcurve; +} + +FCurve *BCAnimationCurve::get_edit_fcurve() +{ + if (!curve_is_local_copy) { + const int index = curve_key.get_array_index(); + const std::string &path = curve_key.get_path(); + fcurve = create_fcurve(index, path.c_str()); + + /* Caution here: + * Replacing the pointer here is OK only because the original value + * of FCurve was a const pointer into Blender territory. We do not + * touch that! We use the local copy to prepare data for export. */ + + curve_is_local_copy = true; + } + return fcurve; +} + +void BCAnimationCurve::clean_handles() +{ + if (fcurve == NULL) { + fcurve = get_edit_fcurve(); + } + + /* Keep old bezt data for copy)*/ + BezTriple *old_bezts = fcurve->bezt; + int totvert = fcurve->totvert; + fcurve->bezt = NULL; + fcurve->totvert = 0; + + for (int i = 0; i < totvert; i++) { + BezTriple *bezt = &old_bezts[i]; + float x = bezt->vec[1][0]; + float y = bezt->vec[1][1]; + insert_vert_fcurve(fcurve, x, y, (eBezTriple_KeyframeType)BEZKEYTYPE(bezt), INSERTKEY_NOFLAGS); + BezTriple *lastb = fcurve->bezt + (fcurve->totvert - 1); + lastb->f1 = lastb->f2 = lastb->f3 = 0; + } + + /* now free the memory used by the old BezTriples */ + if (old_bezts) { + MEM_freeN(old_bezts); + } +} + +const bool BCAnimationCurve::is_transform_curve() const +{ + std::string channel_type = this->get_channel_type(); + return (is_rotation_curve() || channel_type == "scale" || channel_type == "location"); +} + +const bool BCAnimationCurve::is_rotation_curve() const +{ + std::string channel_type = this->get_channel_type(); + return (channel_type == "rotation" || channel_type == "rotation_euler" || + channel_type == "rotation_quaternion"); +} + +const float BCAnimationCurve::get_value(const float frame) +{ + if (fcurve) { + return evaluate_fcurve(fcurve, frame); + } + return 0; // TODO: handle case where neither sample nor fcu exist +} + +void BCAnimationCurve::update_range(float val) +{ + if (val < min) { + min = val; + } + if (val > max) { + max = val; + } +} + +void BCAnimationCurve::init_range(float val) +{ + min = max = val; +} + +void BCAnimationCurve::adjust_range(const int frame_index) +{ + if (fcurve && fcurve->totvert > 1) { + const float eval = evaluate_fcurve(fcurve, frame_index); + + int first_frame = fcurve->bezt[0].vec[1][0]; + if (first_frame == frame_index) { + init_range(eval); + } + else { + update_range(eval); + } + } +} + +void BCAnimationCurve::add_value(const float val, const int frame_index) +{ + FCurve *fcu = get_edit_fcurve(); + fcu->auto_smoothing = U.auto_smoothing_new; + insert_vert_fcurve(fcu, frame_index, val, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_NOFLAGS); + + if (fcu->totvert == 1) { + init_range(val); + } + else { + update_range(val); + } +} + +bool BCAnimationCurve::add_value_from_matrix(const BCSample &sample, const int frame_index) +{ + int array_index = curve_key.get_array_index(); + + /* transformation curves are fed directly from the transformation matrix + * to resolve parent inverse matrix issues with object hierarchies. + * Maybe this can be unified with the + */ + const std::string channel_target = get_channel_target(); + float val = 0; + /* Pick the value from the sample according to the definition of the FCurve */ + bool good = sample.get_value(channel_target, array_index, &val); + if (good) { + add_value(val, frame_index); + } + return good; +} + +bool BCAnimationCurve::add_value_from_rna(const int frame_index) +{ + PointerRNA ptr; + PropertyRNA *prop; + float value = 0.0f; + int array_index = curve_key.get_array_index(); + const std::string full_path = curve_key.get_full_path(); + + /* get property to read from, and get value as appropriate */ + bool path_resolved = RNA_path_resolve_full( + &id_ptr, full_path.c_str(), &ptr, &prop, &array_index); + if (!path_resolved && array_index == 0) { + const std::string rna_path = curve_key.get_path(); + path_resolved = RNA_path_resolve_full(&id_ptr, rna_path.c_str(), &ptr, &prop, &array_index); + } + + if (path_resolved) { + bool is_array = RNA_property_array_check(prop); + if (is_array) { + /* array */ + if ((array_index >= 0) && (array_index < RNA_property_array_length(&ptr, prop))) { + switch (RNA_property_type(prop)) { + case PROP_BOOLEAN: + value = (float)RNA_property_boolean_get_index(&ptr, prop, array_index); + break; + case PROP_INT: + value = (float)RNA_property_int_get_index(&ptr, prop, array_index); + break; + case PROP_FLOAT: + value = RNA_property_float_get_index(&ptr, prop, array_index); + break; + default: + break; + } + } + else { + fprintf(stderr, + "Out of Bounds while reading data for Curve %s\n", + curve_key.get_full_path().c_str()); + return false; + } + } + else { + /* not an array */ + switch (RNA_property_type(prop)) { + case PROP_BOOLEAN: + value = (float)RNA_property_boolean_get(&ptr, prop); + break; + case PROP_INT: + value = (float)RNA_property_int_get(&ptr, prop); + break; + case PROP_FLOAT: + value = RNA_property_float_get(&ptr, prop); + break; + case PROP_ENUM: + value = (float)RNA_property_enum_get(&ptr, prop); + break; + default: + fprintf(stderr, + "property type %d not supported for Curve %s\n", + RNA_property_type(prop), + curve_key.get_full_path().c_str()); + return false; + break; + } + } + } + else { + /* path couldn't be resolved */ + fprintf(stderr, "Path not recognized for Curve %s\n", curve_key.get_full_path().c_str()); + return false; + } + + add_value(value, frame_index); + return true; +} + +void BCAnimationCurve::get_value_map(BCValueMap &value_map) +{ + value_map.clear(); + if (fcurve == NULL) { + return; + } + + for (int i = 0; i < fcurve->totvert; i++) { + const float frame = fcurve->bezt[i].vec[1][0]; + const float val = fcurve->bezt[i].vec[1][1]; + value_map[frame] = val; + } +} + +void BCAnimationCurve::get_frames(BCFrames &frames) const +{ + frames.clear(); + if (fcurve) { + for (int i = 0; i < fcurve->totvert; i++) { + const float val = fcurve->bezt[i].vec[1][0]; + frames.push_back(val); + } + } +} + +void BCAnimationCurve::get_values(BCValues &values) const +{ + values.clear(); + if (fcurve) { + for (int i = 0; i < fcurve->totvert; i++) { + const float val = fcurve->bezt[i].vec[1][1]; + values.push_back(val); + } + } +} + +bool BCAnimationCurve::is_animated() +{ + static float MIN_DISTANCE = 0.00001; + return fabs(max - min) > MIN_DISTANCE; +} + +bool BCAnimationCurve::is_keyframe(int frame) +{ + if (this->fcurve == NULL) { + return false; + } + + for (int i = 0; i < fcurve->totvert; i++) { + const int cframe = nearbyint(fcurve->bezt[i].vec[1][0]); + if (cframe == frame) { + return true; + } + if (cframe > frame) { + break; + } + } + return false; +} + +/* Needed for adding a BCAnimationCurve into a BCAnimationCurveSet */ +inline bool operator<(const BCAnimationCurve &lhs, const BCAnimationCurve &rhs) +{ + std::string lhtgt = lhs.get_channel_target(); + std::string rhtgt = rhs.get_channel_target(); + if (lhtgt == rhtgt) { + const int lha = lhs.get_channel_index(); + const int rha = rhs.get_channel_index(); + return lha < rha; + } + else { + return lhtgt < rhtgt; + } +} + +BCCurveKey::BCCurveKey() +{ + this->key_type = BC_ANIMATION_TYPE_OBJECT; + this->rna_path = ""; + this->curve_array_index = 0; + this->curve_subindex = -1; +} + +BCCurveKey::BCCurveKey(const BC_animation_type type, + const std::string path, + const int array_index, + const int subindex) +{ + this->key_type = type; + this->rna_path = path; + this->curve_array_index = array_index; + this->curve_subindex = subindex; +} + +void BCCurveKey::operator=(const BCCurveKey &other) +{ + this->key_type = other.key_type; + this->rna_path = other.rna_path; + this->curve_array_index = other.curve_array_index; + this->curve_subindex = other.curve_subindex; +} + +const std::string BCCurveKey::get_full_path() const +{ + return this->rna_path + '[' + std::to_string(this->curve_array_index) + ']'; +} + +const std::string BCCurveKey::get_path() const +{ + return this->rna_path; +} + +const int BCCurveKey::get_array_index() const +{ + return this->curve_array_index; +} + +const int BCCurveKey::get_subindex() const +{ + return this->curve_subindex; +} + +void BCCurveKey::set_object_type(BC_animation_type object_type) +{ + this->key_type = object_type; +} + +const BC_animation_type BCCurveKey::get_animation_type() const +{ + return this->key_type; +} + +const bool BCCurveKey::operator<(const BCCurveKey &other) const +{ + /* needed for using this class as key in maps and sets */ + if (this->key_type != other.key_type) { + return this->key_type < other.key_type; + } + + if (this->curve_subindex != other.curve_subindex) { + return this->curve_subindex < other.curve_subindex; + } + + if (this->rna_path != other.rna_path) { + return this->rna_path < other.rna_path; + } + + return this->curve_array_index < other.curve_array_index; +} + +BCBezTriple::BCBezTriple(BezTriple &bezt) : bezt(bezt) +{ +} + +const float BCBezTriple::get_frame() const +{ + return bezt.vec[1][0]; +} + +const float BCBezTriple::get_time(Scene *scene) const +{ + return FRA2TIME(bezt.vec[1][0]); +} + +const float BCBezTriple::get_value() const +{ + return bezt.vec[1][1]; +} + +const float BCBezTriple::get_angle() const +{ + return RAD2DEGF(get_value()); +} + +void BCBezTriple::get_in_tangent(Scene *scene, float point[2], bool as_angle) const +{ + get_tangent(scene, point, as_angle, 0); +} + +void BCBezTriple::get_out_tangent(Scene *scene, float point[2], bool as_angle) const +{ + get_tangent(scene, point, as_angle, 2); +} + +void BCBezTriple::get_tangent(Scene *scene, float point[2], bool as_angle, int index) const +{ + point[0] = FRA2TIME(bezt.vec[index][0]); + if (bezt.ipo != BEZT_IPO_BEZ) { + /* We're in a mixed interpolation scenario, set zero as it's irrelevant but value might contain + * unused data */ + point[0] = 0; + point[1] = 0; + } + else if (as_angle) { + point[1] = RAD2DEGF(bezt.vec[index][1]); + } + else { + point[1] = bezt.vec[index][1]; + } +} diff --git a/source/blender/io/collada/BCAnimationCurve.h b/source/blender/io/collada/BCAnimationCurve.h new file mode 100644 index 00000000000..7b523ac53ca --- /dev/null +++ b/source/blender/io/collada/BCAnimationCurve.h @@ -0,0 +1,152 @@ +/* + * 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) 2008 Blender Foundation. + * All rights reserved. + */ + +#ifndef __BCANIMATIONCURVE_H__ +#define __BCANIMATIONCURVE_H__ + +#include "collada_utils.h" +#include "BCSampleData.h" + +#include "MEM_guardedalloc.h" + +extern "C" { +#include "BKE_fcurve.h" +#include "BKE_armature.h" +#include "BKE_material.h" +#include "ED_anim_api.h" +#include "ED_keyframing.h" +#include "ED_keyframes_edit.h" +} + +typedef float(TangentPoint)[2]; + +typedef std::set<float> BCFrameSet; +typedef std::vector<float> BCFrames; +typedef std::vector<float> BCValues; +typedef std::vector<float> BCTimes; +typedef std::map<int, float> BCValueMap; + +typedef enum BC_animation_type { + BC_ANIMATION_TYPE_OBJECT, + BC_ANIMATION_TYPE_BONE, + BC_ANIMATION_TYPE_CAMERA, + BC_ANIMATION_TYPE_MATERIAL, + BC_ANIMATION_TYPE_LIGHT, +} BC_animation_type; + +class BCCurveKey { + private: + BC_animation_type key_type; + std::string rna_path; + int curve_array_index; + int curve_subindex; /* only needed for materials */ + + public: + BCCurveKey(); + BCCurveKey(const BC_animation_type type, + const std::string path, + const int array_index, + const int subindex = -1); + void operator=(const BCCurveKey &other); + const std::string get_full_path() const; + const std::string get_path() const; + const int get_array_index() const; + const int get_subindex() const; + void set_object_type(BC_animation_type object_type); + const BC_animation_type get_animation_type() const; + const bool operator<(const BCCurveKey &other) const; +}; + +class BCBezTriple { + public: + BezTriple &bezt; + + BCBezTriple(BezTriple &bezt); + const float get_frame() const; + const float get_time(Scene *scene) const; + const float get_value() const; + const float get_angle() const; + void get_in_tangent(Scene *scene, float point[2], bool as_angle) const; + void get_out_tangent(Scene *scene, float point[2], bool as_angle) const; + void get_tangent(Scene *scene, float point[2], bool as_angle, int index) const; +}; + +class BCAnimationCurve { + private: + BCCurveKey curve_key; + float min = 0; + float max = 0; + + bool curve_is_local_copy = false; + FCurve *fcurve; + PointerRNA id_ptr; + void init_pointer_rna(Object *ob); + void delete_fcurve(FCurve *fcu); + FCurve *create_fcurve(int array_index, const char *rna_path); + void create_bezt(float frame, float output); + void update_range(float val); + void init_range(float val); + + public: + BCAnimationCurve(); + BCAnimationCurve(const BCAnimationCurve &other); + BCAnimationCurve(const BCCurveKey &key, Object *ob); + BCAnimationCurve(BCCurveKey key, Object *ob, FCurve *fcu); + ~BCAnimationCurve(); + + const bool is_of_animation_type(BC_animation_type type) const; + const int get_interpolation_type(float sample_frame) const; + bool is_animated(); + const bool is_transform_curve() const; + const bool is_rotation_curve() const; + bool is_keyframe(int frame); + void adjust_range(int frame); + + const std::string get_animation_name(Object *ob) const; /* xxx: this is collada specific */ + const std::string get_channel_target() const; + const std::string get_channel_type() const; + const std::string get_channel_posebone() const; // returns "" if channel is not a bone channel + + const int get_channel_index() const; + const int get_subindex() const; + const std::string get_rna_path() const; + const FCurve *get_fcurve() const; + const int sample_count() const; + + const float get_value(const float frame); + void get_values(BCValues &values) const; + void get_value_map(BCValueMap &value_map); + + void get_frames(BCFrames &frames) const; + + /* Curve edit functions create a copy of the underlaying FCurve */ + FCurve *get_edit_fcurve(); + bool add_value_from_rna(const int frame); + bool add_value_from_matrix(const BCSample &sample, const int frame); + void add_value(const float val, const int frame); + void clean_handles(); + + /* experimental stuff */ + const int closest_index_above(const float sample_frame, const int start_at) const; + const int closest_index_below(const float sample_frame) const; +}; + +typedef std::map<BCCurveKey, BCAnimationCurve *> BCAnimationCurveMap; + +#endif /* __BCANIMATIONCURVE_H__ */ diff --git a/source/blender/io/collada/BCAnimationSampler.cpp b/source/blender/io/collada/BCAnimationSampler.cpp new file mode 100644 index 00000000000..e6996e95a5b --- /dev/null +++ b/source/blender/io/collada/BCAnimationSampler.cpp @@ -0,0 +1,662 @@ +/* + * 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) 2008 Blender Foundation. + * All rights reserved. + */ + +#include <vector> +#include <map> +#include <algorithm> // std::find + +#include "ExportSettings.h" +#include "BCAnimationCurve.h" +#include "BCAnimationSampler.h" +#include "collada_utils.h" +#include "BCMath.h" + +extern "C" { +#include "BKE_action.h" +#include "BKE_constraint.h" +#include "BKE_key.h" +#include "BKE_main.h" +#include "BKE_lib_id.h" +#include "BKE_material.h" +#include "BLI_listbase.h" +#include "DNA_anim_types.h" +#include "DNA_scene_types.h" +#include "DNA_key_types.h" +#include "DNA_constraint_types.h" +#include "ED_object.h" +} + +static std::string EMPTY_STRING; +static BCAnimationCurveMap BCEmptyAnimationCurves; + +BCAnimationSampler::BCAnimationSampler(BCExportSettings &export_settings, BCObjectSet &object_set) + : export_settings(export_settings) +{ + BCObjectSet::iterator it; + for (it = object_set.begin(); it != object_set.end(); ++it) { + Object *ob = *it; + add_object(ob); + } +} + +BCAnimationSampler::~BCAnimationSampler() +{ + BCAnimationObjectMap::iterator it; + for (it = objects.begin(); it != objects.end(); ++it) { + BCAnimation *animation = it->second; + delete animation; + } +} + +void BCAnimationSampler::add_object(Object *ob) +{ + BlenderContext blender_context = export_settings.get_blender_context(); + BCAnimation *animation = new BCAnimation(blender_context.get_context(), ob); + objects[ob] = animation; + + initialize_keyframes(animation->frame_set, ob); + initialize_curves(animation->curve_map, ob); +} + +BCAnimationCurveMap *BCAnimationSampler::get_curves(Object *ob) +{ + BCAnimation &animation = *objects[ob]; + if (animation.curve_map.size() == 0) { + initialize_curves(animation.curve_map, ob); + } + return &animation.curve_map; +} + +static void get_sample_frames(BCFrameSet &sample_frames, + int sampling_rate, + bool keyframe_at_end, + Scene *scene) +{ + sample_frames.clear(); + + if (sampling_rate < 1) { + return; // no sample frames in this case + } + + float sfra = scene->r.sfra; + float efra = scene->r.efra; + + int frame_index; + for (frame_index = nearbyint(sfra); frame_index < efra; frame_index += sampling_rate) { + sample_frames.insert(frame_index); + } + + if (frame_index >= efra && keyframe_at_end) { + sample_frames.insert(efra); + } +} + +static bool is_object_keyframe(Object *ob, int frame_index) +{ + return false; +} + +static void add_keyframes_from(bAction *action, BCFrameSet &frameset) +{ + if (action) { + FCurve *fcu = NULL; + for (fcu = (FCurve *)action->curves.first; fcu; fcu = fcu->next) { + BezTriple *bezt = fcu->bezt; + for (int i = 0; i < fcu->totvert; bezt++, i++) { + int frame_index = nearbyint(bezt->vec[1][0]); + frameset.insert(frame_index); + } + } + } +} + +void BCAnimationSampler::check_property_is_animated( + BCAnimation &animation, float *ref, float *val, std::string data_path, int length) +{ + for (int array_index = 0; array_index < length; array_index++) { + if (!bc_in_range(ref[length], val[length], 0.00001)) { + BCCurveKey key(BC_ANIMATION_TYPE_OBJECT, data_path, array_index); + BCAnimationCurveMap::iterator it = animation.curve_map.find(key); + if (it == animation.curve_map.end()) { + animation.curve_map[key] = new BCAnimationCurve(key, animation.get_reference()); + } + } + } +} + +void BCAnimationSampler::update_animation_curves(BCAnimation &animation, + BCSample &sample, + Object *ob, + int frame) +{ + BCAnimationCurveMap::iterator it; + for (it = animation.curve_map.begin(); it != animation.curve_map.end(); ++it) { + BCAnimationCurve *curve = it->second; + if (curve->is_transform_curve()) { + curve->add_value_from_matrix(sample, frame); + } + else { + curve->add_value_from_rna(frame); + } + } +} + +BCSample &BCAnimationSampler::sample_object(Object *ob, int frame_index, bool for_opensim) +{ + BCSample &ob_sample = sample_data.add(ob, frame_index); + // if (export_settings.get_apply_global_orientation()) { + // const BCMatrix &global_transform = export_settings.get_global_transform(); + // ob_sample.get_matrix(global_transform); + //} + + if (ob->type == OB_ARMATURE) { + bPoseChannel *pchan; + for (pchan = (bPoseChannel *)ob->pose->chanbase.first; pchan; pchan = pchan->next) { + Bone *bone = pchan->bone; + Matrix bmat; + if (bc_bone_matrix_local_get(ob, bone, bmat, for_opensim)) { + + ob_sample.add_bone_matrix(bone, bmat); + } + } + } + return ob_sample; +} + +void BCAnimationSampler::sample_scene(BCExportSettings &export_settings, bool keyframe_at_end) +{ + BlenderContext blender_context = export_settings.get_blender_context(); + int sampling_rate = export_settings.get_sampling_rate(); + bool for_opensim = export_settings.get_open_sim(); + bool keep_keyframes = export_settings.get_keep_keyframes(); + BC_export_animation_type export_animation_type = export_settings.get_export_animation_type(); + + Scene *scene = blender_context.get_scene(); + BCFrameSet scene_sample_frames; + get_sample_frames(scene_sample_frames, sampling_rate, keyframe_at_end, scene); + BCFrameSet::iterator it; + + int startframe = scene->r.sfra; + int endframe = scene->r.efra; + + for (int frame_index = startframe; frame_index <= endframe; frame_index++) { + /* Loop over all frames and decide for each frame if sampling is necessary */ + bool is_scene_sample_frame = false; + bool needs_update = true; + if (scene_sample_frames.find(frame_index) != scene_sample_frames.end()) { + bc_update_scene(blender_context, frame_index); + needs_update = false; + is_scene_sample_frame = true; + } + + bool needs_sampling = is_scene_sample_frame || keep_keyframes || + export_animation_type == BC_ANIMATION_EXPORT_KEYS; + if (!needs_sampling) { + continue; + } + + BCAnimationObjectMap::iterator obit; + for (obit = objects.begin(); obit != objects.end(); ++obit) { + Object *ob = obit->first; + BCAnimation *animation = obit->second; + BCFrameSet &object_keyframes = animation->frame_set; + if (is_scene_sample_frame || object_keyframes.find(frame_index) != object_keyframes.end()) { + + if (needs_update) { + bc_update_scene(blender_context, frame_index); + needs_update = false; + } + + BCSample &sample = sample_object(ob, frame_index, for_opensim); + update_animation_curves(*animation, sample, ob, frame_index); + } + } + } +} + +bool BCAnimationSampler::is_animated_by_constraint(Object *ob, + ListBase *conlist, + std::set<Object *> &animated_objects) +{ + bConstraint *con; + for (con = (bConstraint *)conlist->first; con; con = con->next) { + ListBase targets = {NULL, NULL}; + + const bConstraintTypeInfo *cti = BKE_constraint_typeinfo_get(con); + + if (!bc_validateConstraints(con)) { + continue; + } + + if (cti && cti->get_constraint_targets) { + bConstraintTarget *ct; + Object *obtar; + cti->get_constraint_targets(con, &targets); + for (ct = (bConstraintTarget *)targets.first; ct; ct = ct->next) { + obtar = ct->tar; + if (obtar) { + if (animated_objects.find(obtar) != animated_objects.end()) { + return true; + } + } + } + } + } + return false; +} + +void BCAnimationSampler::find_depending_animated(std::set<Object *> &animated_objects, + std::set<Object *> &candidates) +{ + bool found_more; + do { + found_more = false; + std::set<Object *>::iterator it; + for (it = candidates.begin(); it != candidates.end(); ++it) { + Object *cob = *it; + ListBase *conlist = get_active_constraints(cob); + if (is_animated_by_constraint(cob, conlist, animated_objects)) { + animated_objects.insert(cob); + candidates.erase(cob); + found_more = true; + break; + } + } + } while (found_more && candidates.size() > 0); +} + +void BCAnimationSampler::get_animated_from_export_set(std::set<Object *> &animated_objects, + LinkNode &export_set) +{ + /* Check if this object is animated. That is: Check if it has its own action, or: + * + * - Check if it has constraints to other objects. + * - at least one of the other objects is animated as well. + */ + + animated_objects.clear(); + std::set<Object *> static_objects; + std::set<Object *> candidates; + + LinkNode *node; + for (node = &export_set; node; node = node->next) { + Object *cob = (Object *)node->link; + if (bc_has_animations(cob)) { + animated_objects.insert(cob); + } + else { + ListBase conlist = cob->constraints; + if (conlist.first) { + candidates.insert(cob); + } + } + } + find_depending_animated(animated_objects, candidates); +} + +void BCAnimationSampler::get_object_frames(BCFrames &frames, Object *ob) +{ + sample_data.get_frames(ob, frames); +} + +void BCAnimationSampler::get_bone_frames(BCFrames &frames, Object *ob, Bone *bone) +{ + sample_data.get_frames(ob, bone, frames); +} + +bool BCAnimationSampler::get_bone_samples(BCMatrixSampleMap &samples, Object *ob, Bone *bone) +{ + sample_data.get_matrices(ob, bone, samples); + return bc_is_animated(samples); +} + +bool BCAnimationSampler::get_object_samples(BCMatrixSampleMap &samples, Object *ob) +{ + sample_data.get_matrices(ob, samples); + return bc_is_animated(samples); +} + +#if 0 +/** + * Add sampled values to #FCurve + * If no #FCurve exists, create a temporary #FCurve; + * \note The temporary #FCurve will later be removed when the + * #BCAnimationSampler is removed (by its destructor). + * + * \param curve: The curve to which the data is added. + * \param matrices: The set of matrix values from where the data is taken. + * \param animation_type: + * - #BC_ANIMATION_EXPORT_SAMPLES: Use all matrix data. + * - #BC_ANIMATION_EXPORT_KEYS: Only take data from matrices for keyframes. + */ +void BCAnimationSampler::add_value_set(BCAnimationCurve &curve, + BCFrameSampleMap &samples, + BC_export_animation_type animation_type) +{ + int array_index = curve.get_array_index(); + const BC_animation_transform_type tm_type = curve.get_transform_type(); + + BCFrameSampleMap::iterator it; + for (it = samples.begin(); it != samples.end(); ++it) { + const int frame_index = nearbyint(it->first); + if (animation_type == BC_ANIMATION_EXPORT_SAMPLES || curve.is_keyframe(frame_index)) { + + const BCSample *sample = it->second; + float val = 0; + + int subindex = curve.get_subindex(); + bool good; + if (subindex == -1) { + good = sample->get_value(tm_type, array_index, &val); + } + else { + good = sample->get_value(tm_type, array_index, &val, subindex); + } + + if (good) { + curve.add_value(val, frame_index); + } + } + } + curve.remove_unused_keyframes(); + curve.calchandles(); +} +#endif + +void BCAnimationSampler::generate_transform(Object *ob, + const BCCurveKey &key, + BCAnimationCurveMap &curves) +{ + BCAnimationCurveMap::const_iterator it = curves.find(key); + if (it == curves.end()) { + curves[key] = new BCAnimationCurve(key, ob); + } +} + +void BCAnimationSampler::generate_transforms(Object *ob, + const std::string prep, + const BC_animation_type type, + BCAnimationCurveMap &curves) +{ + generate_transform(ob, BCCurveKey(type, prep + "location", 0), curves); + generate_transform(ob, BCCurveKey(type, prep + "location", 1), curves); + generate_transform(ob, BCCurveKey(type, prep + "location", 2), curves); + generate_transform(ob, BCCurveKey(type, prep + "rotation_euler", 0), curves); + generate_transform(ob, BCCurveKey(type, prep + "rotation_euler", 1), curves); + generate_transform(ob, BCCurveKey(type, prep + "rotation_euler", 2), curves); + generate_transform(ob, BCCurveKey(type, prep + "scale", 0), curves); + generate_transform(ob, BCCurveKey(type, prep + "scale", 1), curves); + generate_transform(ob, BCCurveKey(type, prep + "scale", 2), curves); +} + +void BCAnimationSampler::generate_transforms(Object *ob, Bone *bone, BCAnimationCurveMap &curves) +{ + std::string prep = "pose.bones[\"" + std::string(bone->name) + "\"]."; + generate_transforms(ob, prep, BC_ANIMATION_TYPE_BONE, curves); + + for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) { + generate_transforms(ob, child, curves); + } +} + +/** + * Collect all keyframes from all animation curves related to the object. + * The bc_get... functions check for NULL and correct object type. + * The #add_keyframes_from() function checks for NULL. + */ +void BCAnimationSampler::initialize_keyframes(BCFrameSet &frameset, Object *ob) +{ + frameset.clear(); + add_keyframes_from(bc_getSceneObjectAction(ob), frameset); + add_keyframes_from(bc_getSceneCameraAction(ob), frameset); + add_keyframes_from(bc_getSceneLightAction(ob), frameset); + + for (int a = 0; a < ob->totcol; a++) { + Material *ma = BKE_object_material_get(ob, a + 1); + add_keyframes_from(bc_getSceneMaterialAction(ma), frameset); + } +} + +void BCAnimationSampler::initialize_curves(BCAnimationCurveMap &curves, Object *ob) +{ + BC_animation_type object_type = BC_ANIMATION_TYPE_OBJECT; + + bAction *action = bc_getSceneObjectAction(ob); + if (action) { + FCurve *fcu = (FCurve *)action->curves.first; + + for (; fcu; fcu = fcu->next) { + object_type = BC_ANIMATION_TYPE_OBJECT; + if (ob->type == OB_ARMATURE) { + char *boneName = BLI_str_quoted_substrN(fcu->rna_path, "pose.bones["); + if (boneName) { + object_type = BC_ANIMATION_TYPE_BONE; + } + } + + /* Adding action curves on object */ + BCCurveKey key(object_type, fcu->rna_path, fcu->array_index); + curves[key] = new BCAnimationCurve(key, ob, fcu); + } + } + + /* Add missing curves */ + object_type = BC_ANIMATION_TYPE_OBJECT; + generate_transforms(ob, EMPTY_STRING, object_type, curves); + if (ob->type == OB_ARMATURE) { + bArmature *arm = (bArmature *)ob->data; + for (Bone *root_bone = (Bone *)arm->bonebase.first; root_bone; root_bone = root_bone->next) { + generate_transforms(ob, root_bone, curves); + } + } + + /* Add curves on Object->data actions */ + action = NULL; + if (ob->type == OB_CAMERA) { + action = bc_getSceneCameraAction(ob); + object_type = BC_ANIMATION_TYPE_CAMERA; + } + else if (ob->type == OB_LAMP) { + action = bc_getSceneLightAction(ob); + object_type = BC_ANIMATION_TYPE_LIGHT; + } + + if (action) { + /* Add light action or Camera action */ + FCurve *fcu = (FCurve *)action->curves.first; + for (; fcu; fcu = fcu->next) { + BCCurveKey key(object_type, fcu->rna_path, fcu->array_index); + curves[key] = new BCAnimationCurve(key, ob, fcu); + } + } + + /* Add curves on Object->material actions*/ + object_type = BC_ANIMATION_TYPE_MATERIAL; + for (int a = 0; a < ob->totcol; a++) { + /* Export Material parameter animations. */ + Material *ma = BKE_object_material_get(ob, a + 1); + if (ma) { + action = bc_getSceneMaterialAction(ma); + if (action) { + /* isMatAnim = true; */ + FCurve *fcu = (FCurve *)action->curves.first; + for (; fcu; fcu = fcu->next) { + BCCurveKey key(object_type, fcu->rna_path, fcu->array_index, a); + curves[key] = new BCAnimationCurve(key, ob, fcu); + } + } + } + } +} + +/* ==================================================================== */ + +BCSample &BCSampleFrame::add(Object *ob) +{ + BCSample *sample = new BCSample(ob); + sampleMap[ob] = sample; + return *sample; +} + +/* Get the matrix for the given key, returns Unity when the key does not exist */ +const BCSample *BCSampleFrame::get_sample(Object *ob) const +{ + BCSampleMap::const_iterator it = sampleMap.find(ob); + if (it == sampleMap.end()) { + return NULL; + } + return it->second; +} + +const BCMatrix *BCSampleFrame::get_sample_matrix(Object *ob) const +{ + BCSampleMap::const_iterator it = sampleMap.find(ob); + if (it == sampleMap.end()) { + return NULL; + } + BCSample *sample = it->second; + return &sample->get_matrix(); +} + +/* Get the matrix for the given Bone, returns Unity when the Objewct is not sampled */ +const BCMatrix *BCSampleFrame::get_sample_matrix(Object *ob, Bone *bone) const +{ + BCSampleMap::const_iterator it = sampleMap.find(ob); + if (it == sampleMap.end()) { + return NULL; + } + + BCSample *sample = it->second; + const BCMatrix *bc_bone = sample->get_matrix(bone); + return bc_bone; +} + +/* Check if the key is in this BCSampleFrame */ +const bool BCSampleFrame::has_sample_for(Object *ob) const +{ + return sampleMap.find(ob) != sampleMap.end(); +} + +/* Check if the Bone is in this BCSampleFrame */ +const bool BCSampleFrame::has_sample_for(Object *ob, Bone *bone) const +{ + const BCMatrix *bc_bone = get_sample_matrix(ob, bone); + return (bc_bone); +} + +/* ==================================================================== */ + +BCSample &BCSampleFrameContainer::add(Object *ob, int frame_index) +{ + BCSampleFrame &frame = sample_frames[frame_index]; + return frame.add(ob); +} + +/* ====================================================== */ +/* Below are the getters which we need to export the data */ +/* ====================================================== */ + +/* Return either the BCSampleFrame or NULL if frame does not exist*/ +BCSampleFrame *BCSampleFrameContainer::get_frame(int frame_index) +{ + BCSampleFrameMap::iterator it = sample_frames.find(frame_index); + BCSampleFrame *frame = (it == sample_frames.end()) ? NULL : &it->second; + return frame; +} + +/* Return a list of all frames that need to be sampled */ +const int BCSampleFrameContainer::get_frames(std::vector<int> &frames) const +{ + frames.clear(); // safety; + BCSampleFrameMap::const_iterator it; + for (it = sample_frames.begin(); it != sample_frames.end(); ++it) { + frames.push_back(it->first); + } + return frames.size(); +} + +const int BCSampleFrameContainer::get_frames(Object *ob, BCFrames &frames) const +{ + frames.clear(); // safety; + BCSampleFrameMap::const_iterator it; + for (it = sample_frames.begin(); it != sample_frames.end(); ++it) { + const BCSampleFrame &frame = it->second; + if (frame.has_sample_for(ob)) { + frames.push_back(it->first); + } + } + return frames.size(); +} + +const int BCSampleFrameContainer::get_frames(Object *ob, Bone *bone, BCFrames &frames) const +{ + frames.clear(); // safety; + BCSampleFrameMap::const_iterator it; + for (it = sample_frames.begin(); it != sample_frames.end(); ++it) { + const BCSampleFrame &frame = it->second; + if (frame.has_sample_for(ob, bone)) { + frames.push_back(it->first); + } + } + return frames.size(); +} + +const int BCSampleFrameContainer::get_samples(Object *ob, BCFrameSampleMap &samples) const +{ + samples.clear(); // safety; + BCSampleFrameMap::const_iterator it; + for (it = sample_frames.begin(); it != sample_frames.end(); ++it) { + const BCSampleFrame &frame = it->second; + const BCSample *sample = frame.get_sample(ob); + if (sample) { + samples[it->first] = sample; + } + } + return samples.size(); +} + +const int BCSampleFrameContainer::get_matrices(Object *ob, BCMatrixSampleMap &samples) const +{ + samples.clear(); // safety; + BCSampleFrameMap::const_iterator it; + for (it = sample_frames.begin(); it != sample_frames.end(); ++it) { + const BCSampleFrame &frame = it->second; + const BCMatrix *matrix = frame.get_sample_matrix(ob); + if (matrix) { + samples[it->first] = matrix; + } + } + return samples.size(); +} + +const int BCSampleFrameContainer::get_matrices(Object *ob, + Bone *bone, + BCMatrixSampleMap &samples) const +{ + samples.clear(); // safety; + BCSampleFrameMap::const_iterator it; + for (it = sample_frames.begin(); it != sample_frames.end(); ++it) { + const BCSampleFrame &frame = it->second; + const BCMatrix *sample = frame.get_sample_matrix(ob, bone); + if (sample) { + samples[it->first] = sample; + } + } + return samples.size(); +} diff --git a/source/blender/io/collada/BCAnimationSampler.h b/source/blender/io/collada/BCAnimationSampler.h new file mode 100644 index 00000000000..96138d0cbca --- /dev/null +++ b/source/blender/io/collada/BCAnimationSampler.h @@ -0,0 +1,194 @@ +/* + * 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. + */ + +#ifndef __BCANIMATIONSAMPLER_H__ +#define __BCANIMATIONSAMPLER_H__ + +#include "BCAnimationCurve.h" +#include "BCSampleData.h" +#include "collada_utils.h" + +extern "C" { +#include "BKE_action.h" +#include "BKE_lib_id.h" +#include "BLI_math_rotation.h" +#include "DNA_action_types.h" +} + +/* Collection of animation curves */ +class BCAnimation { + private: + Object *reference = NULL; + bContext *mContext; + + public: + BCFrameSet frame_set; + BCAnimationCurveMap curve_map; + + BCAnimation(bContext *C, Object *ob) : mContext(C) + { + Main *bmain = CTX_data_main(mContext); + reference = BKE_object_copy(bmain, ob); + } + + ~BCAnimation() + { + BCAnimationCurveMap::iterator it; + for (it = curve_map.begin(); it != curve_map.end(); ++it) { + delete it->second; + } + + if (reference && reference->id.us == 0) { + Main *bmain = CTX_data_main(mContext); + BKE_id_delete(bmain, &reference->id); + } + curve_map.clear(); + } + + Object *get_reference() + { + return reference; + } +}; + +typedef std::map<Object *, BCAnimation *> BCAnimationObjectMap; + +class BCSampleFrame { + + /* Each frame on the timeline that needs to be sampled will have + * one BCSampleFrame where we collect sample information about all objects + * that need to be sampled for that frame. */ + + private: + BCSampleMap sampleMap; + + public: + ~BCSampleFrame() + { + BCSampleMap::iterator it; + for (it = sampleMap.begin(); it != sampleMap.end(); ++it) { + BCSample *sample = it->second; + delete sample; + } + sampleMap.clear(); + } + + BCSample &add(Object *ob); + + /* Following methods return NULL if object is not in the sampleMap*/ + const BCSample *get_sample(Object *ob) const; + const BCMatrix *get_sample_matrix(Object *ob) const; + const BCMatrix *get_sample_matrix(Object *ob, Bone *bone) const; + + const bool has_sample_for(Object *ob) const; + const bool has_sample_for(Object *ob, Bone *bone) const; +}; + +typedef std::map<int, BCSampleFrame> BCSampleFrameMap; + +class BCSampleFrameContainer { + + /* + * The BCSampleFrameContainer stores a map of BCSampleFrame objects + * with the timeline frame as key. + * + * Some details on the purpose: + * An Animation is made of multiple FCurves where each FCurve can + * have multiple keyframes. When we want to export the animation we + * also can decide whether we want to export the keyframes or a set + * of sample frames at equidistant locations (sample period). + * In any case we must resample first need to resample it fully + * to resolve things like: + * + * - animations by constraints + * - animations by drivers + * + * For this purpose we need to step through the entire animation and + * then sample each frame that contains at least one keyFrame or + * sampleFrame. Then for each frame we have to store the transform + * information for all exported objects in a BCSampleframe + * + * The entire set of BCSampleframes is finally collected into + * a BCSampleframneContainer + */ + + private: + BCSampleFrameMap sample_frames; + + public: + ~BCSampleFrameContainer() + { + } + + BCSample &add(Object *ob, int frame_index); + BCSampleFrame *get_frame(int frame_index); // returns NULL if frame does not exist + + const int get_frames(std::vector<int> &frames) const; + const int get_frames(Object *ob, BCFrames &frames) const; + const int get_frames(Object *ob, Bone *bone, BCFrames &frames) const; + + const int get_samples(Object *ob, BCFrameSampleMap &samples) const; + const int get_matrices(Object *ob, BCMatrixSampleMap &matrices) const; + const int get_matrices(Object *ob, Bone *bone, BCMatrixSampleMap &bones) const; +}; + +class BCAnimationSampler { + private: + BCExportSettings &export_settings; + BCSampleFrameContainer sample_data; + BCAnimationObjectMap objects; + + void generate_transform(Object *ob, const BCCurveKey &key, BCAnimationCurveMap &curves); + void generate_transforms(Object *ob, + const std::string prep, + const BC_animation_type type, + BCAnimationCurveMap &curves); + void generate_transforms(Object *ob, Bone *bone, BCAnimationCurveMap &curves); + + void initialize_curves(BCAnimationCurveMap &curves, Object *ob); + void initialize_keyframes(BCFrameSet &frameset, Object *ob); + BCSample &sample_object(Object *ob, int frame_index, bool for_opensim); + void update_animation_curves(BCAnimation &animation, + BCSample &sample, + Object *ob, + int frame_index); + void check_property_is_animated( + BCAnimation &animation, float *ref, float *val, std::string data_path, int length); + + public: + BCAnimationSampler(BCExportSettings &export_settings, BCObjectSet &animated_subset); + ~BCAnimationSampler(); + + void add_object(Object *ob); + + void sample_scene(BCExportSettings &export_settings, bool keyframe_at_end); + + BCAnimationCurveMap *get_curves(Object *ob); + void get_object_frames(BCFrames &frames, Object *ob); + bool get_object_samples(BCMatrixSampleMap &samples, Object *ob); + void get_bone_frames(BCFrames &frames, Object *ob, Bone *bone); + bool get_bone_samples(BCMatrixSampleMap &samples, Object *ob, Bone *bone); + + static void get_animated_from_export_set(std::set<Object *> &animated_objects, + LinkNode &export_set); + static void find_depending_animated(std::set<Object *> &animated_objects, + std::set<Object *> &candidates); + static bool is_animated_by_constraint(Object *ob, + ListBase *conlist, + std::set<Object *> &animated_objects); +}; + +#endif /* __BCANIMATIONSAMPLER_H__ */ diff --git a/source/blender/io/collada/BCMath.cpp b/source/blender/io/collada/BCMath.cpp new file mode 100644 index 00000000000..ec9977c1469 --- /dev/null +++ b/source/blender/io/collada/BCMath.cpp @@ -0,0 +1,244 @@ +/* + * 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) 2008 Blender Foundation. + * All rights reserved. + */ + +#include "BCMath.h" +#include "BlenderContext.h" + +void BCQuat::rotate_to(Matrix &mat_to) +{ + Quat qd; + Matrix matd; + Matrix mati; + Matrix mat_from; + + quat_to_mat4(mat_from, q); + + /* Calculate the difference matrix matd between mat_from and mat_to */ + invert_m4_m4(mati, mat_from); + mul_m4_m4m4(matd, mati, mat_to); + + mat4_to_quat(qd, matd); + + mul_qt_qtqt(q, qd, q); /* rotate to the final rotation to mat_to */ +} + +BCMatrix::BCMatrix(const BCMatrix &mat) +{ + set_transform(mat.matrix); +} + +BCMatrix::BCMatrix(Matrix &mat) +{ + set_transform(mat); +} + +BCMatrix::BCMatrix(Object *ob) +{ + set_transform(ob); +} + +BCMatrix::BCMatrix() +{ + unit(); +} + +BCMatrix::BCMatrix(BC_global_forward_axis global_forward_axis, BC_global_up_axis global_up_axis) +{ + float mrot[3][3]; + float mat[4][4]; + mat3_from_axis_conversion( + BC_DEFAULT_FORWARD, BC_DEFAULT_UP, global_forward_axis, global_up_axis, mrot); + + transpose_m3(mrot); // TODO: Verify that mat3_from_axis_conversion() returns a transposed matrix + copy_m4_m3(mat, mrot); + set_transform(mat); +} + +void BCMatrix::add_transform(const Matrix &mat, bool inverse) +{ + add_transform(this->matrix, mat, this->matrix, inverse); +} + +void BCMatrix::add_transform(const BCMatrix &mat, bool inverse) +{ + add_transform(this->matrix, mat.matrix, this->matrix, inverse); +} + +void BCMatrix::apply_transform(const BCMatrix &mat, bool inverse) +{ + apply_transform(this->matrix, mat.matrix, this->matrix, inverse); +} + +void BCMatrix::add_transform(Matrix &to, const Matrix &transform, const Matrix &from, bool inverse) +{ + if (inverse) { + Matrix globinv; + invert_m4_m4(globinv, transform); + add_transform(to, globinv, from, /*inverse=*/false); + } + else { + mul_m4_m4m4(to, transform, from); + } +} + +void BCMatrix::apply_transform(Matrix &to, + const Matrix &transform, + const Matrix &from, + bool inverse) +{ + Matrix globinv; + invert_m4_m4(globinv, transform); + if (inverse) { + add_transform(to, globinv, from, /*inverse=*/false); + } + else { + mul_m4_m4m4(to, transform, from); + mul_m4_m4m4(to, to, globinv); + } +} + +void BCMatrix::add_inverted_transform(Matrix &to, const Matrix &transform, const Matrix &from) +{ + Matrix workmat; + invert_m4_m4(workmat, transform); + mul_m4_m4m4(to, workmat, from); +} + +void BCMatrix::set_transform(Object *ob) +{ + Matrix lmat; + + BKE_object_matrix_local_get(ob, lmat); + copy_m4_m4(matrix, lmat); + + mat4_decompose(this->loc, this->q, this->size, lmat); + quat_to_compatible_eul(this->rot, ob->rot, this->q); +} + +void BCMatrix::set_transform(Matrix &mat) +{ + copy_m4_m4(matrix, mat); + mat4_decompose(this->loc, this->q, this->size, mat); + quat_to_eul(this->rot, this->q); +} + +void BCMatrix::copy(Matrix &out, Matrix &in) +{ + /* destination comes first: */ + memcpy(out, in, sizeof(Matrix)); +} + +void BCMatrix::transpose(Matrix &mat) +{ + transpose_m4(mat); +} + +void BCMatrix::sanitize(Matrix &mat, int precision) +{ + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + double val = (double)mat[i][j]; + val = double_round(val, precision); + mat[i][j] = (float)val; + } + } +} + +void BCMatrix::sanitize(DMatrix &mat, int precision) +{ + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + mat[i][j] = double_round(mat[i][j], precision); + } + } +} + +void BCMatrix::unit() +{ + unit_m4(this->matrix); + mat4_decompose(this->loc, this->q, this->size, this->matrix); + quat_to_eul(this->rot, this->q); +} + +/* We need double here because the OpenCollada API needs it. + * precision = -1 indicates to not limit the precision. */ +void BCMatrix::get_matrix(DMatrix &mat, const bool transposed, const int precision) const +{ + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + float val = (transposed) ? matrix[j][i] : matrix[i][j]; + if (precision >= 0) { + val = floor((val * pow(10, precision) + 0.5)) / pow(10, precision); + } + mat[i][j] = val; + } + } +} + +void BCMatrix::get_matrix(Matrix &mat, + const bool transposed, + const int precision, + const bool inverted) const +{ + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + float val = (transposed) ? matrix[j][i] : matrix[i][j]; + if (precision >= 0) { + val = floor((val * pow(10, precision) + 0.5)) / pow(10, precision); + } + mat[i][j] = val; + } + } + + if (inverted) { + invert_m4(mat); + } +} + +const bool BCMatrix::in_range(const BCMatrix &other, float distance) const +{ + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + if (fabs(other.matrix[i][j] - matrix[i][j]) > distance) { + return false; + } + } + } + return true; +} + +float (&BCMatrix::location() const)[3] +{ + return loc; +} + +float (&BCMatrix::rotation() const)[3] +{ + return rot; +} + +float (&BCMatrix::scale() const)[3] +{ + return size; +} + +float (&BCMatrix::quat() const)[4] +{ + return q; +} diff --git a/source/blender/io/collada/BCMath.h b/source/blender/io/collada/BCMath.h new file mode 100644 index 00000000000..9ecea85b08c --- /dev/null +++ b/source/blender/io/collada/BCMath.h @@ -0,0 +1,110 @@ +/* + * 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 collada + */ + +#ifndef __BCMATH_H__ +#define __BCMATH_H__ + +#include "BlenderTypes.h" + +extern "C" { +#include "BKE_object.h" +#include "BLI_math.h" +} + +class BCQuat { + private: + mutable Quat q; + + public: + BCQuat(const BCQuat &other) + { + copy_v4_v4(q, other.q); + } + + BCQuat(Quat &other) + { + copy_v4_v4(q, other); + } + + BCQuat() + { + unit_qt(q); + } + + Quat &quat() + { + return q; + } + + void rotate_to(Matrix &mat_to); +}; + +class BCMatrix { + + private: + mutable float matrix[4][4]; + mutable float size[3]; + mutable float rot[3]; + mutable float loc[3]; + mutable float q[4]; + + void unit(); + void copy(Matrix &r, Matrix &a); + + public: + float (&location() const)[3]; + float (&rotation() const)[3]; + float (&scale() const)[3]; + float (&quat() const)[4]; + + BCMatrix(BC_global_forward_axis global_forward_axis, BC_global_up_axis global_up_axis); + BCMatrix(const BCMatrix &mat); + BCMatrix(Matrix &mat); + BCMatrix(Object *ob); + BCMatrix(); + + void get_matrix(DMatrix &matrix, const bool transposed = false, const int precision = -1) const; + void get_matrix(Matrix &matrix, + const bool transposed = false, + const int precision = -1, + const bool inverted = false) const; + void set_transform(Object *ob); + void set_transform(Matrix &mat); + void add_transform(Matrix &to, + const Matrix &transform, + const Matrix &from, + const bool inverted = false); + void apply_transform(Matrix &to, + const Matrix &transform, + const Matrix &from, + const bool inverted = false); + void add_inverted_transform(Matrix &to, const Matrix &transform, const Matrix &from); + void add_transform(const Matrix &matrix, const bool inverted = false); + void add_transform(const BCMatrix &matrix, const bool inverted = false); + void apply_transform(const BCMatrix &matrix, const bool inverted = false); + + const bool in_range(const BCMatrix &other, float distance) const; + + static void sanitize(Matrix &matrix, int precision); + static void sanitize(DMatrix &matrix, int precision); + static void transpose(Matrix &matrix); +}; + +#endif /* __BCMATH_H__ */ diff --git a/source/blender/io/collada/BCSampleData.cpp b/source/blender/io/collada/BCSampleData.cpp new file mode 100644 index 00000000000..7e23a2de00f --- /dev/null +++ b/source/blender/io/collada/BCSampleData.cpp @@ -0,0 +1,97 @@ +/* + * 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) 2008 Blender Foundation. + * All rights reserved. + */ + +#include "BCSampleData.h" +#include "collada_utils.h" + +BCSample::~BCSample() +{ + BCBoneMatrixMap::iterator it; + for (it = bonemats.begin(); it != bonemats.end(); ++it) { + delete it->second; + } +} + +void BCSample::add_bone_matrix(Bone *bone, Matrix &mat) +{ + BCMatrix *matrix; + BCBoneMatrixMap::const_iterator it = bonemats.find(bone); + if (it != bonemats.end()) { + throw std::invalid_argument("bone " + std::string(bone->name) + " already defined before"); + } + matrix = new BCMatrix(mat); + bonemats[bone] = matrix; +} + +/* Get channel value */ +const bool BCSample::get_value(std::string channel_target, const int array_index, float *val) const +{ + std::string bname = bc_string_before(channel_target, "."); + std::string channel_type = bc_string_after(channel_target, "."); + + const BCMatrix *matrix = &obmat; + if (bname != channel_target) { + bname = bname.substr(2); + bname = bc_string_before(bname, "\""); + BCBoneMatrixMap::const_iterator it; + for (it = bonemats.begin(); it != bonemats.end(); ++it) { + Bone *bone = it->first; + if (bname == bone->name) { + matrix = it->second; + break; + } + } + } + else { + matrix = &obmat; + } + + if (channel_type == "location") { + *val = matrix->location()[array_index]; + } + else if (channel_type == "scale") { + *val = matrix->scale()[array_index]; + } + else if (channel_type == "rotation" || channel_type == "rotation_euler") { + *val = matrix->rotation()[array_index]; + } + else if (channel_type == "rotation_quaternion") { + *val = matrix->quat()[array_index]; + } + else { + *val = 0; + return false; + } + + return true; +} + +const BCMatrix *BCSample::get_matrix(Bone *bone) const +{ + BCBoneMatrixMap::const_iterator it = bonemats.find(bone); + if (it == bonemats.end()) { + return NULL; + } + return it->second; +} + +const BCMatrix &BCSample::get_matrix() const +{ + return obmat; +} diff --git a/source/blender/io/collada/BCSampleData.h b/source/blender/io/collada/BCSampleData.h new file mode 100644 index 00000000000..07ecb544c71 --- /dev/null +++ b/source/blender/io/collada/BCSampleData.h @@ -0,0 +1,66 @@ +/* + * 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) 2008 Blender Foundation. + * All rights reserved. + */ + +#ifndef __BCSAMPLEDATA_H__ +#define __BCSAMPLEDATA_H__ + +#include <string> +#include <map> +#include <algorithm> + +#include "ExportSettings.h" +#include "BCSampleData.h" +#include "BCMath.h" + +extern "C" { +#include "BKE_object.h" +#include "BLI_math_rotation.h" +#include "DNA_object_types.h" +#include "DNA_armature_types.h" +#include "DNA_material_types.h" +#include "DNA_light_types.h" +#include "DNA_camera_types.h" +} + +typedef std::map<Bone *, BCMatrix *> BCBoneMatrixMap; + +class BCSample { + private: + BCMatrix obmat; + BCBoneMatrixMap bonemats; /* For Armature animation */ + + public: + BCSample(Object *ob) : obmat(ob) + { + } + + ~BCSample(); + + void add_bone_matrix(Bone *bone, Matrix &mat); + + const bool get_value(std::string channel_target, const int array_index, float *val) const; + const BCMatrix &get_matrix() const; + const BCMatrix *get_matrix(Bone *bone) const; // returns NULL if bone is not animated +}; + +typedef std::map<Object *, BCSample *> BCSampleMap; +typedef std::map<int, const BCSample *> BCFrameSampleMap; +typedef std::map<int, const BCMatrix *> BCMatrixSampleMap; + +#endif /* __BCSAMPLEDATA_H__ */ diff --git a/source/blender/io/collada/BlenderContext.cpp b/source/blender/io/collada/BlenderContext.cpp new file mode 100644 index 00000000000..a9783a9b9c4 --- /dev/null +++ b/source/blender/io/collada/BlenderContext.cpp @@ -0,0 +1,156 @@ +/* + * 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 collada + */ + +#include <vector> + +#include "BlenderContext.h" +#include "ExportSettings.h" + +#include "BKE_scene.h" + +bool bc_is_base_node(LinkNode *export_set, Object *ob, ViewLayer *view_layer) +{ + Object *root = bc_get_highest_exported_ancestor_or_self(export_set, ob, view_layer); + return (root == ob); +} + +/** + * Returns the highest selected ancestor + * returns NULL if no ancestor is selected + * IMPORTANT: This function expects that all exported objects have set: + * ob->id.tag & LIB_TAG_DOIT + */ +Object *bc_get_highest_exported_ancestor_or_self(LinkNode *export_set, + Object *ob, + ViewLayer *view_layer) +{ + Object *ancestor = ob; + while (ob->parent) { + if (bc_is_in_Export_set(export_set, ob->parent, view_layer)) { + ancestor = ob->parent; + } + ob = ob->parent; + } + return ancestor; +} + +void bc_get_children(std::vector<Object *> &child_set, Object *ob, ViewLayer *view_layer) +{ + Base *base; + for (base = (Base *)view_layer->object_bases.first; base; base = base->next) { + Object *cob = base->object; + if (cob->parent == ob) { + switch (ob->type) { + case OB_MESH: + case OB_CAMERA: + case OB_LAMP: + case OB_EMPTY: + case OB_ARMATURE: + child_set.push_back(cob); + default: + break; + } + } + } +} + +bool bc_is_in_Export_set(LinkNode *export_set, Object *ob, ViewLayer *view_layer) +{ + bool to_export = (BLI_linklist_index(export_set, ob) != -1); + + if (!to_export) { + /* Mark this object as to_export even if it is not in the + export list, but it contains children to export */ + + std::vector<Object *> children; + bc_get_children(children, ob, view_layer); + for (int i = 0; i < children.size(); i++) { + if (bc_is_in_Export_set(export_set, children[i], view_layer)) { + to_export = true; + break; + } + } + } + return to_export; +} + +int bc_is_marked(Object *ob) +{ + return ob && (ob->id.tag & LIB_TAG_DOIT); +} + +void bc_remove_mark(Object *ob) +{ + ob->id.tag &= ~LIB_TAG_DOIT; +} + +void bc_set_mark(Object *ob) +{ + ob->id.tag |= LIB_TAG_DOIT; +} + +BlenderContext::BlenderContext(bContext *C) +{ + context = C; + main = CTX_data_main(C); + scene = CTX_data_scene(C); + view_layer = CTX_data_view_layer(C); + depsgraph = nullptr; // create only when needed +} + +bContext *BlenderContext::get_context() +{ + return context; +} + +Depsgraph *BlenderContext::get_depsgraph() +{ + if (!depsgraph) { + depsgraph = BKE_scene_get_depsgraph(main, scene, view_layer, true); + } + return depsgraph; +} + +Scene *BlenderContext::get_scene() +{ + return scene; +} + +Scene *BlenderContext::get_evaluated_scene() +{ + Scene *scene_eval = DEG_get_evaluated_scene(get_depsgraph()); + return scene_eval; +} + +Object *BlenderContext::get_evaluated_object(Object *ob) +{ + Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob); + return ob_eval; +} + +ViewLayer *BlenderContext::get_view_layer() +{ + return view_layer; +} + +Main *BlenderContext::get_main() +{ + return main; +} diff --git a/source/blender/io/collada/BlenderContext.h b/source/blender/io/collada/BlenderContext.h new file mode 100644 index 00000000000..50781e8eede --- /dev/null +++ b/source/blender/io/collada/BlenderContext.h @@ -0,0 +1,73 @@ +/* + * 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 collada + */ + +#ifndef __BLENDERCONTEXT_H__ +#define __BLENDERCONTEXT_H__ + +#ifdef __cplusplus + +extern "C" { +#endif + +#include "DNA_object_types.h" +#include "BLI_linklist.h" +#include "BKE_context.h" +#include "BKE_main.h" +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" +#include "DNA_layer_types.h" +#include "BlenderTypes.h" + +static const BC_global_forward_axis BC_DEFAULT_FORWARD = BC_GLOBAL_FORWARD_Y; +static const BC_global_up_axis BC_DEFAULT_UP = BC_GLOBAL_UP_Z; + +bool bc_is_in_Export_set(LinkNode *export_set, Object *ob, ViewLayer *view_layer); +bool bc_is_base_node(LinkNode *export_set, Object *ob, ViewLayer *view_layer); +Object *bc_get_highest_exported_ancestor_or_self(LinkNode *export_set, + Object *ob, + ViewLayer *view_layer); +int bc_is_marked(Object *ob); +void bc_remove_mark(Object *ob); +void bc_set_mark(Object *ob); + +#ifdef __cplusplus +} + +class BlenderContext { + private: + bContext *context; + Depsgraph *depsgraph; + Scene *scene; + ViewLayer *view_layer; + Main *main; + + public: + BlenderContext(bContext *C); + bContext *get_context(); + Depsgraph *get_depsgraph(); + Scene *get_scene(); + Scene *get_evaluated_scene(); + Object *get_evaluated_object(Object *ob); + ViewLayer *get_view_layer(); + Main *get_main(); +}; +#endif + +#endif diff --git a/source/blender/io/collada/BlenderTypes.h b/source/blender/io/collada/BlenderTypes.h new file mode 100644 index 00000000000..0e024be2374 --- /dev/null +++ b/source/blender/io/collada/BlenderTypes.h @@ -0,0 +1,48 @@ +/* + * 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 collada + */ + +#ifndef __BLENDERTYPES_H__ +#define __BLENDERTYPES_H__ + +typedef float(Vector)[3]; +typedef float(Quat)[4]; +typedef float(Color)[4]; +typedef float(Matrix)[4][4]; +typedef double(DMatrix)[4][4]; + +typedef enum BC_global_forward_axis { + BC_GLOBAL_FORWARD_X = 0, + BC_GLOBAL_FORWARD_Y = 1, + BC_GLOBAL_FORWARD_Z = 2, + BC_GLOBAL_FORWARD_MINUS_X = 3, + BC_GLOBAL_FORWARD_MINUS_Y = 4, + BC_GLOBAL_FORWARD_MINUS_Z = 5 +} BC_global_forward_axis; + +typedef enum BC_global_up_axis { + BC_GLOBAL_UP_X = 0, + BC_GLOBAL_UP_Y = 1, + BC_GLOBAL_UP_Z = 2, + BC_GLOBAL_UP_MINUS_X = 3, + BC_GLOBAL_UP_MINUS_Y = 4, + BC_GLOBAL_UP_MINUS_Z = 5 +} BC_global_up_axis; + +#endif diff --git a/source/blender/io/collada/CMakeLists.txt b/source/blender/io/collada/CMakeLists.txt new file mode 100644 index 00000000000..8ffce9e3e7e --- /dev/null +++ b/source/blender/io/collada/CMakeLists.txt @@ -0,0 +1,147 @@ +# ***** 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) 2006, Blender Foundation +# All rights reserved. +# ***** END GPL LICENSE BLOCK ***** + +remove_strict_flags() +FIND_FILE(OPENCOLLADA_ANIMATION_CLIP + NAMES + COLLADAFWAnimationClip.h + PATHS + ${OPENCOLLADA_INCLUDE_DIRS} + NO_DEFAULT_PATH +) + +if(OPENCOLLADA_ANIMATION_CLIP) + add_definitions(-DWITH_OPENCOLLADA_ANIMATION_CLIP) +endif() + +set(INC + . + ../../blenkernel + ../../blenlib + ../../blentranslation + ../../depsgraph + ../../editors/include + ../../imbuf + ../../makesdna + ../../makesrna + ../../windowmanager + ../../../../intern/guardedalloc + ../../ikplugin + ../../../../intern/iksolver/extern + ../../bmesh +) + +set(INC_SYS + ${OPENCOLLADA_INCLUDE_DIRS} +) + +set(SRC + AnimationClipExporter.cpp + AnimationExporter.cpp + AnimationImporter.cpp + ArmatureExporter.cpp + ArmatureImporter.cpp + BCAnimationCurve.cpp + BCAnimationSampler.cpp + BCMath.cpp + BCSampleData.cpp + BlenderContext.cpp + CameraExporter.cpp + ControllerExporter.cpp + DocumentExporter.cpp + DocumentImporter.cpp + EffectExporter.cpp + ErrorHandler.cpp + ExportSettings.cpp + ExtraHandler.cpp + ExtraTags.cpp + GeometryExporter.cpp + ImageExporter.cpp + ImportSettings.cpp + InstanceWriter.cpp + LightExporter.cpp + MaterialExporter.cpp + Materials.cpp + MeshImporter.cpp + SceneExporter.cpp + SkinInfo.cpp + TransformReader.cpp + TransformWriter.cpp + collada.cpp + collada_internal.cpp + collada_utils.cpp + + AnimationClipExporter.h + AnimationExporter.h + AnimationImporter.h + ArmatureExporter.h + ArmatureImporter.h + BCAnimationCurve.h + BCAnimationSampler.h + BCMath.h + BCSampleData.h + BlenderContext.h + BlenderTypes.h + CameraExporter.h + ControllerExporter.h + DocumentExporter.h + DocumentImporter.h + EffectExporter.h + ErrorHandler.h + ExportSettings.h + ExtraHandler.h + ExtraTags.h + GeometryExporter.h + ImageExporter.h + ImportSettings.h + InstanceWriter.h + LightExporter.h + MaterialExporter.h + Materials.h + MeshImporter.h + SceneExporter.h + SkinInfo.h + TransformReader.h + TransformWriter.h + collada.h + collada_internal.h + collada_utils.h +) + +set(LIB + ${OPENCOLLADA_LIBRARIES} + ${PCRE_LIBRARIES} + ${XML2_LIBRARIES} +) + +if(WITH_BUILDINFO) + add_definitions(-DWITH_BUILDINFO) +endif() + +if(WITH_INTERNATIONAL) + add_definitions(-DWITH_INTERNATIONAL) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX) + # COLLADAFWArray.h gives error with gcc 4.5 + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpermissive") +endif() + +blender_add_lib(bf_collada "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/io/collada/CameraExporter.cpp b/source/blender/io/collada/CameraExporter.cpp new file mode 100644 index 00000000000..74862c44270 --- /dev/null +++ b/source/blender/io/collada/CameraExporter.cpp @@ -0,0 +1,98 @@ +/* + * 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 collada + */ + +#include <string> + +#include "COLLADASWCamera.h" + +extern "C" { +#include "DNA_camera_types.h" +} +#include "CameraExporter.h" + +#include "collada_internal.h" + +CamerasExporter::CamerasExporter(COLLADASW::StreamWriter *sw, BCExportSettings &export_settings) + : COLLADASW::LibraryCameras(sw), export_settings(export_settings) +{ +} + +template<class Functor> +void forEachCameraObjectInExportSet(Scene *sce, Functor &f, LinkNode *export_set) +{ + LinkNode *node; + for (node = export_set; node; node = node->next) { + Object *ob = (Object *)node->link; + + if (ob->type == OB_CAMERA && ob->data) { + f(ob, sce); + } + } +} + +void CamerasExporter::exportCameras(Scene *sce) +{ + openLibrary(); + + forEachCameraObjectInExportSet(sce, *this, this->export_settings.get_export_set()); + + closeLibrary(); +} +void CamerasExporter::operator()(Object *ob, Scene *sce) +{ + Camera *cam = (Camera *)ob->data; + std::string cam_id(get_camera_id(ob)); + std::string cam_name(id_name(cam)); + + switch (cam->type) { + case CAM_PANO: + case CAM_PERSP: { + COLLADASW::PerspectiveOptic persp(mSW); + persp.setXFov(RAD2DEGF(focallength_to_fov(cam->lens, cam->sensor_x)), "xfov"); + persp.setAspectRatio((float)(sce->r.xsch) / (float)(sce->r.ysch), false, "aspect_ratio"); + persp.setZFar(cam->clip_end, false, "zfar"); + persp.setZNear(cam->clip_start, false, "znear"); + COLLADASW::Camera ccam(mSW, &persp, cam_id, cam_name); + exportBlenderProfile(ccam, cam); + addCamera(ccam); + + break; + } + case CAM_ORTHO: + default: { + COLLADASW::OrthographicOptic ortho(mSW); + ortho.setXMag(cam->ortho_scale / 2, "xmag"); + ortho.setAspectRatio((float)(sce->r.xsch) / (float)(sce->r.ysch), false, "aspect_ratio"); + ortho.setZFar(cam->clip_end, false, "zfar"); + ortho.setZNear(cam->clip_start, false, "znear"); + COLLADASW::Camera ccam(mSW, &ortho, cam_id, cam_name); + exportBlenderProfile(ccam, cam); + addCamera(ccam); + break; + } + } +} +bool CamerasExporter::exportBlenderProfile(COLLADASW::Camera &cm, Camera *cam) +{ + cm.addExtraTechniqueParameter("blender", "shiftx", cam->shiftx); + cm.addExtraTechniqueParameter("blender", "shifty", cam->shifty); + cm.addExtraTechniqueParameter("blender", "dof_distance", cam->dof.focus_distance); + return true; +} diff --git a/source/blender/io/collada/CameraExporter.h b/source/blender/io/collada/CameraExporter.h new file mode 100644 index 00000000000..04bcc4a5dad --- /dev/null +++ b/source/blender/io/collada/CameraExporter.h @@ -0,0 +1,46 @@ +/* + * 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 collada + */ + +#ifndef __CAMERAEXPORTER_H__ +#define __CAMERAEXPORTER_H__ + +#include "COLLADASWStreamWriter.h" +#include "COLLADASWLibraryCameras.h" + +extern "C" { +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +} + +#include "ExportSettings.h" +#include "DNA_camera_types.h" + +class CamerasExporter : COLLADASW::LibraryCameras { + public: + CamerasExporter(COLLADASW::StreamWriter *sw, BCExportSettings &export_settings); + void exportCameras(Scene *sce); + void operator()(Object *ob, Scene *sce); + + private: + bool exportBlenderProfile(COLLADASW::Camera &cla, Camera *cam); + BCExportSettings &export_settings; +}; + +#endif diff --git a/source/blender/io/collada/ControllerExporter.cpp b/source/blender/io/collada/ControllerExporter.cpp new file mode 100644 index 00000000000..0119aba7dfd --- /dev/null +++ b/source/blender/io/collada/ControllerExporter.cpp @@ -0,0 +1,649 @@ +/* + * 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 collada + */ + +#include "COLLADASWBaseInputElement.h" +#include "COLLADASWInstanceController.h" +#include "COLLADASWPrimitves.h" +#include "COLLADASWSource.h" + +#include "DNA_action_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" + +#include "BKE_action.h" +#include "BKE_armature.h" + +extern "C" { +#include "BKE_global.h" +#include "BKE_idprop.h" +#include "BKE_lib_id.h" +#include "BKE_mesh.h" +} + +#include "ED_armature.h" + +#include "BLI_listbase.h" + +#include "GeometryExporter.h" +#include "ArmatureExporter.h" +#include "ControllerExporter.h" +#include "SceneExporter.h" + +#include "collada_utils.h" + +bool ControllerExporter::is_skinned_mesh(Object *ob) +{ + return bc_get_assigned_armature(ob) != NULL; +} + +void ControllerExporter::write_bone_URLs(COLLADASW::InstanceController &ins, + Object *ob_arm, + Bone *bone) +{ + if (bc_is_root_bone(bone, this->export_settings.get_deform_bones_only())) { + std::string node_id = translate_id(id_name(ob_arm) + "_" + bone->name); + ins.addSkeleton(COLLADABU::URI(COLLADABU::Utils::EMPTY_STRING, node_id)); + } + else { + for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) { + write_bone_URLs(ins, ob_arm, child); + } + } +} + +bool ControllerExporter::add_instance_controller(Object *ob) +{ + Object *ob_arm = bc_get_assigned_armature(ob); + bArmature *arm = (bArmature *)ob_arm->data; + + const std::string &controller_id = get_controller_id(ob_arm, ob); + + COLLADASW::InstanceController ins(mSW); + ins.setUrl(COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, controller_id)); + + Mesh *me = (Mesh *)ob->data; + if (!me->dvert) { + return false; + } + + /* write root bone URLs */ + Bone *bone; + for (bone = (Bone *)arm->bonebase.first; bone; bone = bone->next) { + write_bone_URLs(ins, ob_arm, bone); + } + + InstanceWriter::add_material_bindings( + ins.getBindMaterial(), ob, this->export_settings.get_active_uv_only()); + + ins.add(); + return true; +} + +void ControllerExporter::export_controllers() +{ + Scene *sce = blender_context.get_scene(); + openLibrary(); + + GeometryFunctor gf; + gf.forEachMeshObjectInExportSet<ControllerExporter>( + sce, *this, this->export_settings.get_export_set()); + + closeLibrary(); +} + +void ControllerExporter::operator()(Object *ob) +{ + Object *ob_arm = bc_get_assigned_armature(ob); + Key *key = BKE_key_from_object(ob); + + if (ob_arm) { + export_skin_controller(ob, ob_arm); + } + if (key && this->export_settings.get_include_shapekeys()) { + export_morph_controller(ob, key); + } +} +#if 0 + +bool ArmatureExporter::already_written(Object *ob_arm) +{ + return std::find(written_armatures.begin(), written_armatures.end(), ob_arm) != + written_armatures.end(); +} + +void ArmatureExporter::wrote(Object *ob_arm) +{ + written_armatures.push_back(ob_arm); +} + +void ArmatureExporter::find_objects_using_armature(Object *ob_arm, + std::vector<Object *> &objects, + Scene *sce) +{ + objects.clear(); + + Base *base = (Base *)sce->base.first; + while (base) { + Object *ob = base->object; + + if (ob->type == OB_MESH && get_assigned_armature(ob) == ob_arm) { + objects.push_back(ob); + } + + base = base->next; + } +} +#endif + +std::string ControllerExporter::get_controller_id(Object *ob_arm, Object *ob) +{ + return translate_id(id_name(ob_arm)) + "_" + translate_id(id_name(ob)) + + SKIN_CONTROLLER_ID_SUFFIX; +} + +std::string ControllerExporter::get_controller_id(Key *key, Object *ob) +{ + return translate_id(id_name(ob)) + MORPH_CONTROLLER_ID_SUFFIX; +} + +/* ob should be of type OB_MESH + * both args are required */ +void ControllerExporter::export_skin_controller(Object *ob, Object *ob_arm) +{ + /* joint names + * joint inverse bind matrices + * vertex weights */ + + /* input: + * joint names: ob -> vertex group names + * vertex group weights: me->dvert -> groups -> index, weight */ + + bool use_instantiation = this->export_settings.get_use_object_instantiation(); + Mesh *me; + + if (((Mesh *)ob->data)->dvert == NULL) { + return; + } + + me = bc_get_mesh_copy(blender_context, + ob, + this->export_settings.get_export_mesh_type(), + this->export_settings.get_apply_modifiers(), + this->export_settings.get_triangulate()); + + std::string controller_name = id_name(ob_arm); + std::string controller_id = get_controller_id(ob_arm, ob); + + openSkin(controller_id, + controller_name, + COLLADABU::URI(COLLADABU::Utils::EMPTY_STRING, get_geometry_id(ob, use_instantiation))); + + add_bind_shape_mat(ob); + + std::string joints_source_id = add_joints_source(ob_arm, &ob->defbase, controller_id); + std::string inv_bind_mat_source_id = add_inv_bind_mats_source( + ob_arm, &ob->defbase, controller_id); + + std::list<int> vcounts; + std::list<int> joints; + std::list<float> weights; + + { + int i, j; + + /* def group index -> joint index */ + std::vector<int> joint_index_by_def_index; + bDeformGroup *def; + + for (def = (bDeformGroup *)ob->defbase.first, i = 0, j = 0; def; def = def->next, i++) { + if (is_bone_defgroup(ob_arm, def)) { + joint_index_by_def_index.push_back(j++); + } + else { + joint_index_by_def_index.push_back(-1); + } + } + + int oob_counter = 0; + for (i = 0; i < me->totvert; i++) { + MDeformVert *vert = &me->dvert[i]; + std::map<int, float> jw; + + /* We're normalizing the weights later */ + float sumw = 0.0f; + + for (j = 0; j < vert->totweight; j++) { + uint idx = vert->dw[j].def_nr; + if (idx >= joint_index_by_def_index.size()) { + /* XXX: Maybe better find out where and + * why the Out Of Bound indexes get created ? */ + oob_counter += 1; + } + else { + int joint_index = joint_index_by_def_index[idx]; + if (joint_index != -1 && vert->dw[j].weight > 0.0f) { + jw[joint_index] += vert->dw[j].weight; + sumw += vert->dw[j].weight; + } + } + } + + if (sumw > 0.0f) { + float invsumw = 1.0f / sumw; + vcounts.push_back(jw.size()); + for (std::map<int, float>::iterator m = jw.begin(); m != jw.end(); ++m) { + joints.push_back((*m).first); + weights.push_back(invsumw * (*m).second); + } + } + else { + vcounts.push_back(0); +#if 0 + vcounts.push_back(1); + joints.push_back(-1); + weights.push_back(1.0f); +#endif + } + } + + if (oob_counter > 0) { + fprintf(stderr, + "Ignored %d Vertex weights which use index to non existing VGroup %zu.\n", + oob_counter, + joint_index_by_def_index.size()); + } + } + + std::string weights_source_id = add_weights_source(me, controller_id, weights); + add_joints_element(&ob->defbase, joints_source_id, inv_bind_mat_source_id); + add_vertex_weights_element(weights_source_id, joints_source_id, vcounts, joints); + + BKE_id_free(NULL, me); + + closeSkin(); + closeController(); +} + +void ControllerExporter::export_morph_controller(Object *ob, Key *key) +{ + bool use_instantiation = this->export_settings.get_use_object_instantiation(); + Mesh *me; + + me = bc_get_mesh_copy(blender_context, + ob, + this->export_settings.get_export_mesh_type(), + this->export_settings.get_apply_modifiers(), + this->export_settings.get_triangulate()); + + std::string controller_name = id_name(ob) + "-morph"; + std::string controller_id = get_controller_id(key, ob); + + openMorph( + controller_id, + controller_name, + COLLADABU::URI(COLLADABU::Utils::EMPTY_STRING, get_geometry_id(ob, use_instantiation))); + + std::string targets_id = add_morph_targets(key, ob); + std::string morph_weights_id = add_morph_weights(key, ob); + + COLLADASW::TargetsElement targets(mSW); + + COLLADASW::InputList &input = targets.getInputList(); + + input.push_back(COLLADASW::Input( + COLLADASW::InputSemantic::MORPH_TARGET, // constant declared in COLLADASWInputList.h + COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, targets_id))); + input.push_back( + COLLADASW::Input(COLLADASW::InputSemantic::MORPH_WEIGHT, + COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, morph_weights_id))); + targets.add(); + + BKE_id_free(NULL, me); + + /* support for animations + * can also try the base element and param alternative */ + add_weight_extras(key); + closeMorph(); + closeController(); +} + +std::string ControllerExporter::add_morph_targets(Key *key, Object *ob) +{ + std::string source_id = translate_id(id_name(ob)) + TARGETS_SOURCE_ID_SUFFIX; + + COLLADASW::IdRefSource source(mSW); + source.setId(source_id); + source.setArrayId(source_id + ARRAY_ID_SUFFIX); + source.setAccessorCount(key->totkey - 1); + source.setAccessorStride(1); + + COLLADASW::SourceBase::ParameterNameList ¶m = source.getParameterNameList(); + param.push_back("IDREF"); + + source.prepareToAppendValues(); + + KeyBlock *kb = (KeyBlock *)key->block.first; + /* skip the basis */ + kb = kb->next; + for (; kb; kb = kb->next) { + std::string geom_id = get_geometry_id(ob, false) + "_morph_" + translate_id(kb->name); + source.appendValues(geom_id); + } + + source.finish(); + + return source_id; +} + +std::string ControllerExporter::add_morph_weights(Key *key, Object *ob) +{ + std::string source_id = translate_id(id_name(ob)) + WEIGHTS_SOURCE_ID_SUFFIX; + + COLLADASW::FloatSourceF source(mSW); + source.setId(source_id); + source.setArrayId(source_id + ARRAY_ID_SUFFIX); + source.setAccessorCount(key->totkey - 1); + source.setAccessorStride(1); + + COLLADASW::SourceBase::ParameterNameList ¶m = source.getParameterNameList(); + param.push_back("MORPH_WEIGHT"); + + source.prepareToAppendValues(); + + KeyBlock *kb = (KeyBlock *)key->block.first; + /* skip the basis */ + kb = kb->next; + for (; kb; kb = kb->next) { + float weight = kb->curval; + source.appendValues(weight); + } + source.finish(); + + return source_id; +} + +/* Added to implement support for animations. */ +void ControllerExporter::add_weight_extras(Key *key) +{ + /* can also try the base element and param alternative */ + COLLADASW::BaseExtraTechnique extra; + + KeyBlock *kb = (KeyBlock *)key->block.first; + /* skip the basis */ + kb = kb->next; + for (; kb; kb = kb->next) { + /* XXX why is the weight not used here and set to 0.0? + * float weight = kb->curval; */ + extra.addExtraTechniqueParameter("KHR", "morph_weights", 0.000, "MORPH_WEIGHT_TO_TARGET"); + } +} + +void ControllerExporter::add_joints_element(ListBase *defbase, + const std::string &joints_source_id, + const std::string &inv_bind_mat_source_id) +{ + COLLADASW::JointsElement joints(mSW); + COLLADASW::InputList &input = joints.getInputList(); + + input.push_back(COLLADASW::Input( + COLLADASW::InputSemantic::JOINT, // constant declared in COLLADASWInputList.h + COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, joints_source_id))); + input.push_back( + COLLADASW::Input(COLLADASW::InputSemantic::BINDMATRIX, + COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, inv_bind_mat_source_id))); + joints.add(); +} + +void ControllerExporter::add_bind_shape_mat(Object *ob) +{ + double bind_mat[4][4]; + float f_obmat[4][4]; + BKE_object_matrix_local_get(ob, f_obmat); + + if (export_settings.get_apply_global_orientation()) { + // do nothing, rotation is going to be applied to the Data + } + else { + bc_add_global_transform(f_obmat, export_settings.get_global_transform()); + } + + // UnitConverter::mat4_to_dae_double(bind_mat, ob->obmat); + UnitConverter::mat4_to_dae_double(bind_mat, f_obmat); + if (this->export_settings.get_limit_precision()) { + BCMatrix::sanitize(bind_mat, LIMITTED_PRECISION); + } + + addBindShapeTransform(bind_mat); +} + +std::string ControllerExporter::add_joints_source(Object *ob_arm, + ListBase *defbase, + const std::string &controller_id) +{ + std::string source_id = controller_id + JOINTS_SOURCE_ID_SUFFIX; + + int totjoint = 0; + bDeformGroup *def; + for (def = (bDeformGroup *)defbase->first; def; def = def->next) { + if (is_bone_defgroup(ob_arm, def)) { + totjoint++; + } + } + + COLLADASW::NameSource source(mSW); + source.setId(source_id); + source.setArrayId(source_id + ARRAY_ID_SUFFIX); + source.setAccessorCount(totjoint); + source.setAccessorStride(1); + + COLLADASW::SourceBase::ParameterNameList ¶m = source.getParameterNameList(); + param.push_back("JOINT"); + + source.prepareToAppendValues(); + + for (def = (bDeformGroup *)defbase->first; def; def = def->next) { + Bone *bone = get_bone_from_defgroup(ob_arm, def); + if (bone) { + source.appendValues(get_joint_sid(bone)); + } + } + + source.finish(); + + return source_id; +} + +std::string ControllerExporter::add_inv_bind_mats_source(Object *ob_arm, + ListBase *defbase, + const std::string &controller_id) +{ + std::string source_id = controller_id + BIND_POSES_SOURCE_ID_SUFFIX; + + int totjoint = 0; + for (bDeformGroup *def = (bDeformGroup *)defbase->first; def; def = def->next) { + if (is_bone_defgroup(ob_arm, def)) { + totjoint++; + } + } + + COLLADASW::FloatSourceF source(mSW); + source.setId(source_id); + source.setArrayId(source_id + ARRAY_ID_SUFFIX); + source.setAccessorCount(totjoint); // BLI_listbase_count(defbase)); + source.setAccessorStride(16); + + source.setParameterTypeName(&COLLADASW::CSWC::CSW_VALUE_TYPE_FLOAT4x4); + COLLADASW::SourceBase::ParameterNameList ¶m = source.getParameterNameList(); + param.push_back("TRANSFORM"); + + source.prepareToAppendValues(); + + bPose *pose = ob_arm->pose; + bArmature *arm = (bArmature *)ob_arm->data; + + int flag = arm->flag; + + /* put armature in rest position */ + if (!(arm->flag & ARM_RESTPOS)) { + Depsgraph *depsgraph = blender_context.get_depsgraph(); + Scene *scene = blender_context.get_scene(); + + arm->flag |= ARM_RESTPOS; + BKE_pose_where_is(depsgraph, scene, ob_arm); + } + + for (bDeformGroup *def = (bDeformGroup *)defbase->first; def; def = def->next) { + if (is_bone_defgroup(ob_arm, def)) { + bPoseChannel *pchan = BKE_pose_channel_find_name(pose, def->name); + + float mat[4][4]; + float world[4][4]; + float inv_bind_mat[4][4]; + + float bind_mat[4][4]; /* derived from bone->arm_mat */ + + bool has_bindmat = bc_get_property_matrix(pchan->bone, "bind_mat", bind_mat); + + if (!has_bindmat) { + + /* Have no bind matrix stored, try old style <= Blender 2.78 */ + + bc_create_restpose_mat( + this->export_settings, pchan->bone, bind_mat, pchan->bone->arm_mat, true); + + /* SL/OPEN_SIM COMPATIBILITY */ + if (export_settings.get_open_sim()) { + float loc[3]; + float rot[3] = {0, 0, 0}; + float scale[3]; + bc_decompose(bind_mat, loc, NULL, NULL, scale); + + /* Only translations, no rotation vs armature */ + loc_eulO_size_to_mat4(bind_mat, loc, rot, scale, 6); + } + } + + /* make world-space matrix (bind_mat is armature-space) */ + mul_m4_m4m4(world, ob_arm->obmat, bind_mat); + + if (!has_bindmat) { + if (export_settings.get_apply_global_orientation()) { + bc_apply_global_transform(world, export_settings.get_global_transform()); + } + } + + invert_m4_m4(mat, world); + UnitConverter::mat4_to_dae(inv_bind_mat, mat); + if (this->export_settings.get_limit_precision()) { + BCMatrix::sanitize(inv_bind_mat, LIMITTED_PRECISION); + } + source.appendValues(inv_bind_mat); + } + } + + /* back from rest position */ + if (!(flag & ARM_RESTPOS)) { + Depsgraph *depsgraph = blender_context.get_depsgraph(); + Scene *scene = blender_context.get_scene(); + arm->flag = flag; + BKE_pose_where_is(depsgraph, scene, ob_arm); + } + + source.finish(); + + return source_id; +} + +Bone *ControllerExporter::get_bone_from_defgroup(Object *ob_arm, bDeformGroup *def) +{ + bPoseChannel *pchan = BKE_pose_channel_find_name(ob_arm->pose, def->name); + return pchan ? pchan->bone : NULL; +} + +bool ControllerExporter::is_bone_defgroup(Object *ob_arm, bDeformGroup *def) +{ + return get_bone_from_defgroup(ob_arm, def) != NULL; +} + +std::string ControllerExporter::add_weights_source(Mesh *me, + const std::string &controller_id, + const std::list<float> &weights) +{ + std::string source_id = controller_id + WEIGHTS_SOURCE_ID_SUFFIX; + + COLLADASW::FloatSourceF source(mSW); + source.setId(source_id); + source.setArrayId(source_id + ARRAY_ID_SUFFIX); + source.setAccessorCount(weights.size()); + source.setAccessorStride(1); + + COLLADASW::SourceBase::ParameterNameList ¶m = source.getParameterNameList(); + param.push_back("WEIGHT"); + + source.prepareToAppendValues(); + + for (std::list<float>::const_iterator i = weights.begin(); i != weights.end(); ++i) { + source.appendValues(*i); + } + + source.finish(); + + return source_id; +} + +void ControllerExporter::add_vertex_weights_element(const std::string &weights_source_id, + const std::string &joints_source_id, + const std::list<int> &vcounts, + const std::list<int> &joints) +{ + COLLADASW::VertexWeightsElement weightselem(mSW); + COLLADASW::InputList &input = weightselem.getInputList(); + + int offset = 0; + input.push_back(COLLADASW::Input( + COLLADASW::InputSemantic::JOINT, // constant declared in COLLADASWInputList.h + COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, joints_source_id), + offset++)); + input.push_back( + COLLADASW::Input(COLLADASW::InputSemantic::WEIGHT, + COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, weights_source_id), + offset++)); + + weightselem.setCount(vcounts.size()); + + /* write number of deformers per vertex */ + COLLADASW::PrimitivesBase::VCountList vcountlist; + + vcountlist.resize(vcounts.size()); + std::copy(vcounts.begin(), vcounts.end(), vcountlist.begin()); + + weightselem.prepareToAppendVCountValues(); + weightselem.appendVertexCount(vcountlist); + + weightselem.CloseVCountAndOpenVElement(); + + /* write deformer index - weight index pairs */ + int weight_index = 0; + for (std::list<int>::const_iterator i = joints.begin(); i != joints.end(); ++i) { + weightselem.appendValues(*i, weight_index++); + } + + weightselem.finish(); +} diff --git a/source/blender/io/collada/ControllerExporter.h b/source/blender/io/collada/ControllerExporter.h new file mode 100644 index 00000000000..ce2ed9fe453 --- /dev/null +++ b/source/blender/io/collada/ControllerExporter.h @@ -0,0 +1,137 @@ +/* + * 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 collada + */ + +#ifndef __CONTROLLEREXPORTER_H__ +#define __CONTROLLEREXPORTER_H__ + +#include <list> +#include <string> +//#include <vector> + +#include "COLLADASWStreamWriter.h" +#include "COLLADASWLibraryControllers.h" +#include "COLLADASWInstanceController.h" +#include "COLLADASWInputList.h" +#include "COLLADASWNode.h" +#include "COLLADASWExtraTechnique.h" + +#include "DNA_armature_types.h" +#include "DNA_listBase.h" +#include "DNA_mesh_types.h" +#include "DNA_object_types.h" +#include "DNA_constraint_types.h" +#include "DNA_scene_types.h" +#include "DNA_key_types.h" + +#include "TransformWriter.h" +#include "InstanceWriter.h" + +#include "ExportSettings.h" + +#include "BKE_key.h" + +class SceneExporter; + +class ControllerExporter : public COLLADASW::LibraryControllers, + protected TransformWriter, + protected InstanceWriter { + private: + BlenderContext &blender_context; + BCExportSettings export_settings; + + public: + // XXX exporter writes wrong data for shared armatures. A separate + // controller should be written for each armature-mesh binding how do + // we make controller ids then? + ControllerExporter(BlenderContext &blender_context, + COLLADASW::StreamWriter *sw, + BCExportSettings &export_settings) + : COLLADASW::LibraryControllers(sw), + blender_context(blender_context), + export_settings(export_settings) + { + } + + bool is_skinned_mesh(Object *ob); + + bool add_instance_controller(Object *ob); + + void export_controllers(); + + void operator()(Object *ob); + + private: +#if 0 + std::vector<Object *> written_armatures; + + bool already_written(Object *ob_arm); + + void wrote(Object *ob_arm); + + void find_objects_using_armature(Object *ob_arm, std::vector<Object *> &objects, Scene *sce); +#endif + + std::string get_controller_id(Object *ob_arm, Object *ob); + + std::string get_controller_id(Key *key, Object *ob); + + // ob should be of type OB_MESH + // both args are required + void export_skin_controller(Object *ob, Object *ob_arm); + + void export_morph_controller(Object *ob, Key *key); + + void add_joints_element(ListBase *defbase, + const std::string &joints_source_id, + const std::string &inv_bind_mat_source_id); + + void add_bind_shape_mat(Object *ob); + + std::string add_morph_targets(Key *key, Object *ob); + + std::string add_morph_weights(Key *key, Object *ob); + + void add_weight_extras(Key *key); + + std::string add_joints_source(Object *ob_arm, + ListBase *defbase, + const std::string &controller_id); + + std::string add_inv_bind_mats_source(Object *ob_arm, + ListBase *defbase, + const std::string &controller_id); + + Bone *get_bone_from_defgroup(Object *ob_arm, bDeformGroup *def); + + bool is_bone_defgroup(Object *ob_arm, bDeformGroup *def); + + std::string add_weights_source(Mesh *me, + const std::string &controller_id, + const std::list<float> &weights); + + void add_vertex_weights_element(const std::string &weights_source_id, + const std::string &joints_source_id, + const std::list<int> &vcount, + const std::list<int> &joints); + + void write_bone_URLs(COLLADASW::InstanceController &ins, Object *ob_arm, Bone *bone); +}; + +#endif diff --git a/source/blender/io/collada/DocumentExporter.cpp b/source/blender/io/collada/DocumentExporter.cpp new file mode 100644 index 00000000000..24a960ab287 --- /dev/null +++ b/source/blender/io/collada/DocumentExporter.cpp @@ -0,0 +1,346 @@ +/* + * 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 collada + */ + +#include <stdlib.h> +#include <stdio.h> +#include <math.h> +#include <vector> +#include <algorithm> // std::find + +#include "COLLADASWCamera.h" +#include "COLLADASWAsset.h" +#include "COLLADASWLibraryVisualScenes.h" +#include "COLLADASWNode.h" +#include "COLLADASWSource.h" +#include "COLLADASWInstanceGeometry.h" +#include "COLLADASWInputList.h" +#include "COLLADASWPrimitves.h" +#include "COLLADASWVertices.h" +#include "COLLADASWLibraryAnimations.h" +#include "COLLADASWLibraryImages.h" +#include "COLLADASWLibraryEffects.h" +#include "COLLADASWImage.h" +#include "COLLADASWEffectProfile.h" +#include "COLLADASWColorOrTexture.h" +#include "COLLADASWParamTemplate.h" +#include "COLLADASWParamBase.h" +#include "COLLADASWSurfaceInitOption.h" +#include "COLLADASWSampler.h" +#include "COLLADASWScene.h" +#include "COLLADASWTechnique.h" +#include "COLLADASWTexture.h" +#include "COLLADASWLibraryMaterials.h" +#include "COLLADASWBindMaterial.h" +#include "COLLADASWInstanceCamera.h" +#include "COLLADASWInstanceLight.h" +#include "COLLADASWConstants.h" +#include "COLLADASWLibraryControllers.h" +#include "COLLADASWInstanceController.h" +#include "COLLADASWInstanceNode.h" +#include "COLLADASWBaseInputElement.h" + +#include "MEM_guardedalloc.h" + +extern "C" { +#include "DNA_scene_types.h" +#include "DNA_object_types.h" +#include "DNA_collection_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_mesh_types.h" +#include "DNA_image_types.h" +#include "DNA_material_types.h" +#include "DNA_anim_types.h" +#include "DNA_action_types.h" +#include "DNA_curve_types.h" +#include "DNA_armature_types.h" +#include "DNA_modifier_types.h" +#include "DNA_userdef_types.h" + +#include "BLI_path_util.h" +#include "BLI_fileops.h" +#include "BLI_math.h" +#include "BLI_string.h" +#include "BLI_listbase.h" +#include "BLI_utildefines.h" + +#include "BKE_action.h" // pose functions +#include "BKE_animsys.h" +#include "BKE_armature.h" +#include "BKE_blender_version.h" +#include "BKE_customdata.h" +#include "BKE_fcurve.h" +#include "BKE_global.h" +#include "BKE_image.h" +#include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_object.h" +#include "BKE_scene.h" +#include "BKE_appdir.h" + +#include "ED_keyframing.h" +#ifdef WITH_BUILDINFO +extern char build_commit_date[]; +extern char build_commit_time[]; +extern char build_hash[]; +#endif + +#include "RNA_access.h" +} + +#include "collada_internal.h" +#include "collada_utils.h" +#include "DocumentExporter.h" + +extern bool bc_has_object_type(LinkNode *export_set, short obtype); + +// can probably go after refactor is complete +#include "InstanceWriter.h" +#include "TransformWriter.h" + +#include "SceneExporter.h" +#include "ArmatureExporter.h" +#include "AnimationExporter.h" +#include "CameraExporter.h" +#include "ControllerExporter.h" +#include "EffectExporter.h" +#include "GeometryExporter.h" +#include "ImageExporter.h" +#include "LightExporter.h" +#include "MaterialExporter.h" + +#include <errno.h> + +char *bc_CustomData_get_layer_name(const struct CustomData *data, int type, int n) +{ + int layer_index = CustomData_get_layer_index(data, type); + if (layer_index < 0) { + return NULL; + } + + return data->layers[layer_index + n].name; +} + +char *bc_CustomData_get_active_layer_name(const CustomData *data, int type) +{ + /* get the layer index of the active layer of type */ + int layer_index = CustomData_get_active_layer_index(data, type); + if (layer_index < 0) { + return NULL; + } + + return data->layers[layer_index].name; +} + +DocumentExporter::DocumentExporter(BlenderContext &blender_context, ExportSettings *exportSettings) + : blender_context(blender_context), + export_settings(BCExportSettings(exportSettings, blender_context)) +{ +} + +static COLLADABU::NativeString make_temp_filepath(const char *name, const char *extension) +{ + char tempfile[FILE_MAX]; + const char *tempdir = BKE_tempdir_session(); + + if (name == NULL) { + name = "untitled"; + } + + BLI_make_file_string(NULL, tempfile, tempdir, name); + + if (extension) { + BLI_path_extension_ensure(tempfile, FILE_MAX, extension); + } + + COLLADABU::NativeString native_filename = COLLADABU::NativeString( + tempfile, COLLADABU::NativeString::ENCODING_UTF8); + return native_filename; +} + +// TODO: it would be better to instantiate animations rather than create a new one per object +// COLLADA allows this through multiple <channel>s in <animation>. +// For this to work, we need to know objects that use a certain action. + +int DocumentExporter::exportCurrentScene() +{ + Scene *sce = blender_context.get_scene(); + bContext *C = blender_context.get_context(); + + PointerRNA sceneptr, unit_settings; + PropertyRNA *system; /* unused , *scale; */ + + clear_global_id_map(); + + COLLADABU::NativeString native_filename = make_temp_filepath(NULL, ".dae"); + COLLADASW::StreamWriter *writer = new COLLADASW::StreamWriter(native_filename); + + // open <collada> + writer->startDocument(); + + // <asset> + COLLADASW::Asset asset(writer); + + RNA_id_pointer_create(&(sce->id), &sceneptr); + unit_settings = RNA_pointer_get(&sceneptr, "unit_settings"); + system = RNA_struct_find_property(&unit_settings, "system"); + // scale = RNA_struct_find_property(&unit_settings, "scale_length"); + + std::string unitname = "meter"; + float linearmeasure = RNA_float_get(&unit_settings, "scale_length"); + + switch (RNA_property_enum_get(&unit_settings, system)) { + case USER_UNIT_NONE: + case USER_UNIT_METRIC: + if (linearmeasure == 0.001f) { + unitname = "millimeter"; + } + else if (linearmeasure == 0.01f) { + unitname = "centimeter"; + } + else if (linearmeasure == 0.1f) { + unitname = "decimeter"; + } + else if (linearmeasure == 1.0f) { + unitname = "meter"; + } + else if (linearmeasure == 1000.0f) { + unitname = "kilometer"; + } + break; + case USER_UNIT_IMPERIAL: + if (linearmeasure == 0.0254f) { + unitname = "inch"; + } + else if (linearmeasure == 0.3048f) { + unitname = "foot"; + } + else if (linearmeasure == 0.9144f) { + unitname = "yard"; + } + break; + default: + break; + } + + asset.setUnit(unitname, linearmeasure); + asset.setUpAxisType(COLLADASW::Asset::Z_UP); + asset.getContributor().mAuthor = "Blender User"; + char version_buf[128]; +#ifdef WITH_BUILDINFO + BLI_snprintf(version_buf, + sizeof(version_buf), + "Blender %d.%02d.%d commit date:%s, commit time:%s, hash:%s", + BLENDER_VERSION / 100, + BLENDER_VERSION % 100, + BLENDER_SUBVERSION, + build_commit_date, + build_commit_time, + build_hash); +#else + BLI_snprintf(version_buf, + sizeof(version_buf), + "Blender %d.%02d.%d", + BLENDER_VERSION / 100, + BLENDER_VERSION % 100, + BLENDER_SUBVERSION); +#endif + asset.getContributor().mAuthoringTool = version_buf; + asset.add(); + + LinkNode *export_set = this->export_settings.get_export_set(); + // <library_cameras> + if (bc_has_object_type(export_set, OB_CAMERA)) { + CamerasExporter ce(writer, this->export_settings); + ce.exportCameras(sce); + } + + // <library_lights> + if (bc_has_object_type(export_set, OB_LAMP)) { + LightsExporter le(writer, this->export_settings); + le.exportLights(sce); + } + + // <library_effects> + EffectsExporter ee(writer, this->export_settings, key_image_map); + ee.exportEffects(C, sce); + + // <library_images> + ImagesExporter ie(writer, this->export_settings, key_image_map); + ie.exportImages(sce); + + // <library_materials> + MaterialsExporter me(writer, this->export_settings); + me.exportMaterials(sce); + + // <library_geometries> + if (bc_has_object_type(export_set, OB_MESH)) { + GeometryExporter ge(blender_context, writer, this->export_settings); + ge.exportGeom(); + } + + // <library_controllers> + ArmatureExporter arm_exporter(blender_context, writer, this->export_settings); + ControllerExporter controller_exporter(blender_context, writer, this->export_settings); + if (bc_has_object_type(export_set, OB_ARMATURE) || + this->export_settings.get_include_shapekeys()) { + controller_exporter.export_controllers(); + } + + // <library_visual_scenes> + + SceneExporter se(blender_context, writer, &arm_exporter, this->export_settings); + + if (this->export_settings.get_include_animations()) { + // <library_animations> + AnimationExporter ae(writer, this->export_settings); + ae.exportAnimations(); + } + + se.exportScene(); + + // <scene> + std::string scene_name(translate_id(id_name(sce))); + COLLADASW::Scene scene(writer, COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, scene_name)); + scene.add(); + + // close <Collada> + writer->endDocument(); + delete writer; + + // Finally move the created document into place + fprintf(stdout, "Collada export to: %s\n", this->export_settings.get_filepath()); + int status = BLI_rename(native_filename.c_str(), this->export_settings.get_filepath()); + if (status != 0) { + status = BLI_copy(native_filename.c_str(), this->export_settings.get_filepath()); + BLI_delete(native_filename.c_str(), false, false); + } + return status; +} + +void DocumentExporter::exportScenes(const char *filename) +{ +} + +/* + * NOTES: + * + * AnimationExporter::sample_animation enables all curves on armature, this is undesirable for a + * user + */ diff --git a/source/blender/io/collada/DocumentExporter.h b/source/blender/io/collada/DocumentExporter.h new file mode 100644 index 00000000000..70722ae601e --- /dev/null +++ b/source/blender/io/collada/DocumentExporter.h @@ -0,0 +1,44 @@ +/* + * 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 collada + */ + +#ifndef __DOCUMENTEXPORTER_H__ +#define __DOCUMENTEXPORTER_H__ + +#include "collada.h" +#include "collada_utils.h" +#include "BlenderContext.h" + +extern "C" { +#include "DNA_customdata_types.h" +} + +class DocumentExporter { + public: + DocumentExporter(BlenderContext &blender_context, ExportSettings *export_settings); + int exportCurrentScene(); + void exportScenes(const char *filename); + + private: + BlenderContext &blender_context; + BCExportSettings export_settings; + KeyImageMap key_image_map; +}; + +#endif diff --git a/source/blender/io/collada/DocumentImporter.cpp b/source/blender/io/collada/DocumentImporter.cpp new file mode 100644 index 00000000000..9b66ff429e1 --- /dev/null +++ b/source/blender/io/collada/DocumentImporter.cpp @@ -0,0 +1,1265 @@ +/* + * 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 collada + */ + +/* TODO: + * * name imported objects + * * import object rotation as euler */ + +#include <string> +#include <map> +#include <algorithm> // sort() + +#include "COLLADAFWRoot.h" +#include "COLLADAFWStableHeaders.h" +#include "COLLADAFWColorOrTexture.h" +#include "COLLADAFWIndexList.h" +#include "COLLADAFWMeshPrimitiveWithFaceVertexCount.h" +#include "COLLADAFWPolygons.h" +#include "COLLADAFWSampler.h" +#include "COLLADAFWTypes.h" +#include "COLLADAFWVisualScene.h" +#include "COLLADAFWArrayPrimitiveType.h" +#include "COLLADAFWLibraryNodes.h" +#include "COLLADAFWCamera.h" +#include "COLLADAFWLight.h" + +#include "COLLADASaxFWLLoader.h" +#include "COLLADASaxFWLIExtraDataCallbackHandler.h" + +#include "MEM_guardedalloc.h" + +extern "C" { +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_string.h" +#include "BLI_utildefines.h" +#include "BLI_fileops.h" + +#include "BKE_camera.h" +#include "BKE_collection.h" +#include "BKE_fcurve.h" +#include "BKE_global.h" +#include "BKE_image.h" +#include "BKE_layer.h" +#include "BKE_light.h" +#include "BKE_lib_id.h" +#include "BKE_material.h" +#include "BKE_scene.h" + +#include "BLI_path_util.h" + +#include "DNA_camera_types.h" +#include "DNA_light_types.h" + +#include "RNA_access.h" + +#include "WM_api.h" +#include "WM_types.h" +} + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" + +#include "ExtraHandler.h" +#include "ErrorHandler.h" +#include "DocumentImporter.h" +#include "TransformReader.h" + +#include "collada_internal.h" +#include "collada_utils.h" +#include "Materials.h" + +/* + * COLLADA Importer limitations: + * - no multiple scene import, all objects are added to active scene + */ + +// #define COLLADA_DEBUG +// creates empties for each imported bone on layer 2, for debugging +// #define ARMATURE_TEST + +DocumentImporter::DocumentImporter(bContext *C, const ImportSettings *import_settings) + : import_settings(import_settings), + mImportStage(Fetching_Scene_data), + mContext(C), + view_layer(CTX_data_view_layer(mContext)), + armature_importer(&unit_converter, + &mesh_importer, + CTX_data_main(C), + CTX_data_scene(C), + view_layer, + import_settings), + mesh_importer( + &unit_converter, &armature_importer, CTX_data_main(C), CTX_data_scene(C), view_layer), + anim_importer(C, &unit_converter, &armature_importer, CTX_data_scene(C)) +{ +} + +DocumentImporter::~DocumentImporter() +{ + TagsMap::iterator etit; + etit = uid_tags_map.begin(); + while (etit != uid_tags_map.end()) { + delete etit->second; + etit++; + } +} + +bool DocumentImporter::import() +{ + ErrorHandler errorHandler; + COLLADASaxFWL::Loader loader(&errorHandler); + COLLADAFW::Root root(&loader, this); + ExtraHandler *ehandler = new ExtraHandler(this, &(this->anim_importer)); + + loader.registerExtraDataCallbackHandler(ehandler); + + /* deselect all to select new objects */ + BKE_view_layer_base_deselect_all(view_layer); + + std::string mFilename = std::string(this->import_settings->filepath); + const std::string encodedFilename = bc_url_encode(mFilename); + if (!root.loadDocument(encodedFilename)) { + fprintf(stderr, "COLLADAFW::Root::loadDocument() returned false on 1st pass\n"); + delete ehandler; + return false; + } + + if (errorHandler.hasError()) { + delete ehandler; + return false; + } + + /** TODO set up scene graph and such here */ + mImportStage = Fetching_Controller_data; + COLLADASaxFWL::Loader loader2; + COLLADAFW::Root root2(&loader2, this); + + if (!root2.loadDocument(encodedFilename)) { + fprintf(stderr, "COLLADAFW::Root::loadDocument() returned false on 2nd pass\n"); + delete ehandler; + return false; + } + + delete ehandler; + + return true; +} + +void DocumentImporter::cancel(const COLLADAFW::String &errorMessage) +{ + /* TODO: if possible show error info + * + * Should we get rid of invisible Meshes that were created so far + * or maybe create objects at coordinate space origin? + * + * The latter sounds better. */ +} + +void DocumentImporter::start() +{ +} + +void DocumentImporter::finish() +{ + if (mImportStage == Fetching_Controller_data) { + return; + } + + Main *bmain = CTX_data_main(mContext); + /* TODO: create a new scene except the selected <visual_scene> - + * use current blender scene for it */ + Scene *sce = CTX_data_scene(mContext); + unit_converter.calculate_scale(*sce); + + std::vector<Object *> *objects_to_scale = new std::vector<Object *>(); + + /** TODO Break up and put into 2-pass parsing of DAE */ + std::vector<const COLLADAFW::VisualScene *>::iterator sit; + for (sit = vscenes.begin(); sit != vscenes.end(); sit++) { + PointerRNA sceneptr, unit_settings; + PropertyRNA *system, *scale; + + /* for scene unit settings: system, scale_length */ + + RNA_id_pointer_create(&sce->id, &sceneptr); + unit_settings = RNA_pointer_get(&sceneptr, "unit_settings"); + system = RNA_struct_find_property(&unit_settings, "system"); + scale = RNA_struct_find_property(&unit_settings, "scale_length"); + + if (this->import_settings->import_units) { + + switch (unit_converter.isMetricSystem()) { + case UnitConverter::Metric: + RNA_property_enum_set(&unit_settings, system, USER_UNIT_METRIC); + break; + case UnitConverter::Imperial: + RNA_property_enum_set(&unit_settings, system, USER_UNIT_IMPERIAL); + break; + default: + RNA_property_enum_set(&unit_settings, system, USER_UNIT_NONE); + break; + } + float unit_factor = unit_converter.getLinearMeter(); + RNA_property_float_set(&unit_settings, scale, unit_factor); + fprintf(stdout, "Collada: Adjusting Blender units to Importset units: %f.\n", unit_factor); + } + + /* Write nodes to scene */ + fprintf(stderr, "+-- Import Scene --------\n"); + const COLLADAFW::NodePointerArray &roots = (*sit)->getRootNodes(); + for (unsigned int i = 0; i < roots.getCount(); i++) { + std::vector<Object *> *objects_done = write_node(roots[i], NULL, sce, NULL, false); + objects_to_scale->insert( + objects_to_scale->end(), objects_done->begin(), objects_done->end()); + delete objects_done; + } + } + + mesh_importer.optimize_material_assignements(); + + armature_importer.set_tags_map(this->uid_tags_map); + armature_importer.make_armatures(mContext, *objects_to_scale); + armature_importer.make_shape_keys(mContext); + +#if 0 + armature_importer.fix_animation(); +#endif + + for (std::vector<const COLLADAFW::VisualScene *>::iterator vsit = vscenes.begin(); + vsit != vscenes.end(); + vsit++) { + const COLLADAFW::NodePointerArray &roots = (*vsit)->getRootNodes(); + + for (unsigned int i = 0; i < roots.getCount(); i++) { + translate_anim_recursive(roots[i], NULL, NULL); + } + } + + if (libnode_ob.size()) { + + fprintf(stderr, "| Cleanup: free %d library nodes\n", (int)libnode_ob.size()); + /* free all library_nodes */ + std::vector<Object *>::iterator it; + for (it = libnode_ob.begin(); it != libnode_ob.end(); it++) { + Object *ob = *it; + BKE_scene_collections_object_remove(bmain, sce, ob, true); + } + libnode_ob.clear(); + } + + bc_match_scale(objects_to_scale, unit_converter, !this->import_settings->import_units); + + delete objects_to_scale; + + /* update scene */ + DEG_id_tag_update(&sce->id, ID_RECALC_COPY_ON_WRITE); + DEG_relations_tag_update(bmain); + WM_event_add_notifier(mContext, NC_OBJECT | ND_TRANSFORM, NULL); +} + +void DocumentImporter::translate_anim_recursive(COLLADAFW::Node *node, + COLLADAFW::Node *par = NULL, + Object *parob = NULL) +{ + /* The split in #29246, rootmap must point at actual root when + * calculating bones in apply_curves_as_matrix. - actual root is the root node. + * This has to do with inverse bind poses being world space + * (the sources for skinned bones' restposes) and the way + * non-skinning nodes have their "restpose" recursively calculated. + * XXX TODO: design issue, how to support unrelated joints taking + * part in skinning. */ + if (par) { // && par->getType() == COLLADAFW::Node::JOINT) { + /* par is root if there's no corresp. key in root_map */ + if (root_map.find(par->getUniqueId()) == root_map.end()) { + root_map[node->getUniqueId()] = node; + } + else { + root_map[node->getUniqueId()] = root_map[par->getUniqueId()]; + } + } + +#if 0 + COLLADAFW::Transformation::TransformationType types[] = { + COLLADAFW::Transformation::ROTATE, + COLLADAFW::Transformation::SCALE, + COLLADAFW::Transformation::TRANSLATE, + COLLADAFW::Transformation::MATRIX, + }; + + Object *ob; +#endif + unsigned int i; + + if (node->getType() == COLLADAFW::Node::JOINT && par == NULL) { + /* For Skeletons without root node we have to simulate the + * root node here and recursively enter the same function + * XXX: maybe this can be made more elegant. */ + translate_anim_recursive(node, node, parob); + } + else { + anim_importer.translate_Animations( + node, root_map, object_map, FW_object_map, uid_material_map); + COLLADAFW::NodePointerArray &children = node->getChildNodes(); + for (i = 0; i < children.getCount(); i++) { + translate_anim_recursive(children[i], node, NULL); + } + } +} + +/** + * If the imported file was made with Blender, return the Blender version used, + * otherwise return an empty std::string + */ +std::string DocumentImporter::get_import_version(const COLLADAFW::FileInfo *asset) +{ + const char AUTORING_TOOL[] = "authoring_tool"; + const std::string BLENDER("Blender "); + const COLLADAFW::FileInfo::ValuePairPointerArray &valuePairs = asset->getValuePairArray(); + for (size_t i = 0, count = valuePairs.getCount(); i < count; i++) { + const COLLADAFW::FileInfo::ValuePair *valuePair = valuePairs[i]; + const COLLADAFW::String &key = valuePair->first; + const COLLADAFW::String &value = valuePair->second; + if (key == AUTORING_TOOL) { + if (value.compare(0, BLENDER.length(), BLENDER) == 0) { + /* Was made with Blender, now get version string */ + std::string v = value.substr(BLENDER.length()); + std::string::size_type n = v.find(" "); + if (n > 0) { + return v.substr(0, n); + } + } + } + } + return ""; +} + +/** + * When this method is called, the writer must write the global document asset. + * \return The writer should return true, if writing succeeded, false otherwise. + */ +bool DocumentImporter::writeGlobalAsset(const COLLADAFW::FileInfo *asset) +{ + unit_converter.read_asset(asset); + import_from_version = get_import_version(asset); + anim_importer.set_import_from_version(import_from_version); + return true; +} + +/** + * When this method is called, the writer must write the scene. + * \return The writer should return true, if writing succeeded, false otherwise. + */ +bool DocumentImporter::writeScene(const COLLADAFW::Scene *scene) +{ + /* XXX could store the scene id, but do nothing for now */ + return true; +} +Object *DocumentImporter::create_camera_object(COLLADAFW::InstanceCamera *camera, Scene *sce) +{ + const COLLADAFW::UniqueId &cam_uid = camera->getInstanciatedObjectId(); + if (uid_camera_map.find(cam_uid) == uid_camera_map.end()) { + // fprintf(stderr, "Couldn't find camera by UID.\n"); + return NULL; + } + + Main *bmain = CTX_data_main(mContext); + Object *ob = bc_add_object(bmain, sce, view_layer, OB_CAMERA, NULL); + Camera *cam = uid_camera_map[cam_uid]; + Camera *old_cam = (Camera *)ob->data; + ob->data = cam; + BKE_id_free_us(bmain, old_cam); + return ob; +} + +Object *DocumentImporter::create_light_object(COLLADAFW::InstanceLight *lamp, Scene *sce) +{ + const COLLADAFW::UniqueId &lamp_uid = lamp->getInstanciatedObjectId(); + if (uid_light_map.find(lamp_uid) == uid_light_map.end()) { + fprintf(stderr, "Couldn't find light by UID.\n"); + return NULL; + } + + Main *bmain = CTX_data_main(mContext); + Object *ob = bc_add_object(bmain, sce, view_layer, OB_LAMP, NULL); + Light *la = uid_light_map[lamp_uid]; + Light *old_light = (Light *)ob->data; + ob->data = la; + BKE_id_free_us(bmain, old_light); + return ob; +} + +Object *DocumentImporter::create_instance_node(Object *source_ob, + COLLADAFW::Node *source_node, + COLLADAFW::Node *instance_node, + Scene *sce, + bool is_library_node) +{ + // fprintf(stderr, "create <instance_node> under node id=%s from node id=%s\n", instance_node ? + // instance_node->getOriginalId().c_str() : NULL, source_node ? + // source_node->getOriginalId().c_str() : NULL); + + Main *bmain = CTX_data_main(mContext); + Object *obn = BKE_object_copy(bmain, source_ob); + DEG_id_tag_update(&obn->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION); + BKE_collection_object_add_from(bmain, sce, source_ob, obn); + + if (instance_node) { + anim_importer.read_node_transform(instance_node, obn); + /* if we also have a source_node (always ;), take its + * transformation matrix and apply it to the newly instantiated + * object to account for node hierarchy transforms in + * .dae */ + if (source_node) { + COLLADABU::Math::Matrix4 mat4 = source_node->getTransformationMatrix(); + COLLADABU::Math::Matrix4 bmat4 = + mat4.transpose(); // transpose to get blender row-major order + float mat[4][4]; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + mat[i][j] = bmat4[i][j]; + } + } + /* calc new matrix and apply */ + mul_m4_m4m4(obn->obmat, obn->obmat, mat); + BKE_object_apply_mat4(obn, obn->obmat, 0, 0); + } + } + else { + anim_importer.read_node_transform(source_node, obn); + } + + /*DAG_relations_tag_update(CTX_data_main(mContext));*/ + + COLLADAFW::NodePointerArray &children = source_node->getChildNodes(); + if (children.getCount()) { + for (unsigned int i = 0; i < children.getCount(); i++) { + COLLADAFW::Node *child_node = children[i]; + const COLLADAFW::UniqueId &child_id = child_node->getUniqueId(); + if (object_map.find(child_id) == object_map.end()) { + continue; + } + COLLADAFW::InstanceNodePointerArray &inodes = child_node->getInstanceNodes(); + Object *new_child = NULL; + if (inodes.getCount()) { // \todo loop through instance nodes + const COLLADAFW::UniqueId &id = inodes[0]->getInstanciatedObjectId(); + fprintf(stderr, "Doing %d child nodes\n", (int)node_map.count(id)); + new_child = create_instance_node( + object_map.find(id)->second, node_map[id], child_node, sce, is_library_node); + } + else { + new_child = create_instance_node( + object_map.find(child_id)->second, child_node, NULL, sce, is_library_node); + } + bc_set_parent(new_child, obn, mContext, true); + + if (is_library_node) { + libnode_ob.push_back(new_child); + } + } + } + + return obn; +} + +/* to create constraints off node <extra> tags. Assumes only constraint data in + * current <extra> with blender profile. */ +void DocumentImporter::create_constraints(ExtraTags *et, Object *ob) +{ + if (et && et->isProfile("blender")) { + std::string name; + short type = 0; + et->setData("type", &type); + BKE_constraint_add_for_object(ob, "Test_con", type); + } +} + +void DocumentImporter::report_unknown_reference(const COLLADAFW::Node &node, + const std::string object_type) +{ + std::string id = node.getOriginalId(); + std::string name = node.getName(); + fprintf(stderr, + "error: node id=\"%s\", name=\"%s\" refers to an undefined %s.\n", + id.c_str(), + name.c_str(), + object_type.c_str()); +} + +std::vector<Object *> *DocumentImporter::write_node(COLLADAFW::Node *node, + COLLADAFW::Node *parent_node, + Scene *sce, + Object *par, + bool is_library_node) +{ + Main *bmain = CTX_data_main(mContext); + Object *ob = NULL; + bool is_joint = node->getType() == COLLADAFW::Node::JOINT; + bool read_transform = true; + std::string id = node->getOriginalId(); + std::string name = node->getName(); + + /* if node has child nodes write them */ + COLLADAFW::NodePointerArray &child_nodes = node->getChildNodes(); + + std::vector<Object *> *objects_done = new std::vector<Object *>(); + std::vector<Object *> *root_objects = new std::vector<Object *>(); + + fprintf( + stderr, "| %s id='%s', name='%s'\n", is_joint ? "JOINT" : "NODE ", id.c_str(), name.c_str()); + + if (is_joint) { + if (parent_node == NULL && !is_library_node) { + /* A Joint on root level is a skeleton without root node. + * Here we add the armature "on the fly": */ + par = bc_add_object(bmain, sce, view_layer, OB_ARMATURE, std::string("Armature").c_str()); + objects_done->push_back(par); + root_objects->push_back(par); + object_map.insert(std::pair<COLLADAFW::UniqueId, Object *>(node->getUniqueId(), par)); + node_map[node->getUniqueId()] = node; + } + if (parent_node == NULL || parent_node->getType() != COLLADAFW::Node::JOINT) { + armature_importer.add_root_joint(node, par); + } + + if (parent_node == NULL) { + /* for skeletons without root node all has been done above. + * Skeletons with root node are handled further down. */ + goto finally; + } + } + else { + COLLADAFW::InstanceGeometryPointerArray &geom = node->getInstanceGeometries(); + COLLADAFW::InstanceCameraPointerArray &camera = node->getInstanceCameras(); + COLLADAFW::InstanceLightPointerArray &lamp = node->getInstanceLights(); + COLLADAFW::InstanceControllerPointerArray &controller = node->getInstanceControllers(); + COLLADAFW::InstanceNodePointerArray &inst_node = node->getInstanceNodes(); + size_t geom_done = 0; + size_t camera_done = 0; + size_t lamp_done = 0; + size_t controller_done = 0; + size_t inst_done = 0; + + /* XXX linking object with the first <instance_geometry>, though a node may have more of + * them... maybe join multiple <instance_...> meshes into 1, and link object with it? not + * sure... <instance_geometry> */ + while (geom_done < geom.getCount()) { + ob = mesh_importer.create_mesh_object(node, geom[geom_done], false, uid_material_map); + if (ob == NULL) { + report_unknown_reference(*node, "instance_mesh"); + } + else { + objects_done->push_back(ob); + if (parent_node == NULL) { + root_objects->push_back(ob); + } + } + geom_done++; + } + while (camera_done < camera.getCount()) { + ob = create_camera_object(camera[camera_done], sce); + if (ob == NULL) { + report_unknown_reference(*node, "instance_camera"); + } + else { + objects_done->push_back(ob); + if (parent_node == NULL) { + root_objects->push_back(ob); + } + } + camera_done++; + } + while (lamp_done < lamp.getCount()) { + ob = create_light_object(lamp[lamp_done], sce); + if (ob == NULL) { + report_unknown_reference(*node, "instance_light"); + } + else { + objects_done->push_back(ob); + if (parent_node == NULL) { + root_objects->push_back(ob); + } + } + lamp_done++; + } + while (controller_done < controller.getCount()) { + COLLADAFW::InstanceGeometry *geometry = (COLLADAFW::InstanceGeometry *) + controller[controller_done]; + ob = mesh_importer.create_mesh_object(node, geometry, true, uid_material_map); + if (ob == NULL) { + report_unknown_reference(*node, "instance_controller"); + } + else { + objects_done->push_back(ob); + if (parent_node == NULL) { + root_objects->push_back(ob); + } + } + controller_done++; + } + /* XXX instance_node is not supported yet */ + while (inst_done < inst_node.getCount()) { + const COLLADAFW::UniqueId &node_id = inst_node[inst_done]->getInstanciatedObjectId(); + if (object_map.find(node_id) == object_map.end()) { + fprintf(stderr, + "Cannot find object for node referenced by <instance_node name=\"%s\">.\n", + inst_node[inst_done]->getName().c_str()); + ob = NULL; + } + else { + std::pair<std::multimap<COLLADAFW::UniqueId, Object *>::iterator, + std::multimap<COLLADAFW::UniqueId, Object *>::iterator> + pair_iter = object_map.equal_range(node_id); + for (std::multimap<COLLADAFW::UniqueId, Object *>::iterator it2 = pair_iter.first; + it2 != pair_iter.second; + it2++) { + Object *source_ob = (Object *)it2->second; + COLLADAFW::Node *source_node = node_map[node_id]; + ob = create_instance_node(source_ob, source_node, node, sce, is_library_node); + objects_done->push_back(ob); + if (parent_node == NULL) { + root_objects->push_back(ob); + } + } + } + inst_done++; + + read_transform = false; + } + + /* if node is empty - create empty object + * XXX empty node may not mean it is empty object, not sure about this */ + if ((geom_done + camera_done + lamp_done + controller_done + inst_done) < 1) { + /* Check if Object is armature, by checking if immediate child is a JOINT node. */ + if (is_armature(node)) { + ob = bc_add_object(bmain, sce, view_layer, OB_ARMATURE, name.c_str()); + } + else { + ob = bc_add_object(bmain, sce, view_layer, OB_EMPTY, NULL); + } + objects_done->push_back(ob); + if (parent_node == NULL) { + root_objects->push_back(ob); + } + } + + /* XXX: if there're multiple instances, only one is stored */ + + if (!ob) { + goto finally; + } + + for (std::vector<Object *>::iterator it = objects_done->begin(); it != objects_done->end(); + ++it) { + ob = *it; + std::string nodename = node->getName().size() ? node->getName() : node->getOriginalId(); + BKE_libblock_rename(bmain, &ob->id, (char *)nodename.c_str()); + object_map.insert(std::pair<COLLADAFW::UniqueId, Object *>(node->getUniqueId(), ob)); + node_map[node->getUniqueId()] = node; + + if (is_library_node) { + libnode_ob.push_back(ob); + } + } + + // create_constraints(et,ob); + } + + for (std::vector<Object *>::iterator it = objects_done->begin(); it != objects_done->end(); + ++it) { + ob = *it; + + if (read_transform) { + anim_importer.read_node_transform(node, ob); // overwrites location set earlier + } + + if (!is_joint) { + if (par && ob) { + ob->parent = par; + ob->partype = PAROBJECT; + ob->parsubstr[0] = 0; + + // bc_set_parent(ob, par, mContext, false); + } + } + } + + if (objects_done->size() > 0) { + ob = *objects_done->begin(); + } + else { + ob = NULL; + } + + for (unsigned int i = 0; i < child_nodes.getCount(); i++) { + std::vector<Object *> *child_objects; + child_objects = write_node(child_nodes[i], node, sce, ob, is_library_node); + delete child_objects; + } + +finally: + delete objects_done; + + return root_objects; +} + +/** + * When this method is called, the writer must write the entire visual scene. + * Return The writer should return true, if writing succeeded, false otherwise. + */ +bool DocumentImporter::writeVisualScene(const COLLADAFW::VisualScene *visualScene) +{ + if (mImportStage == Fetching_Controller_data) { + return true; + } + + /* This method called on post process after writeGeometry, writeMaterial, etc. */ + + /* For each <node> in <visual_scene>: + * create an Object + * if Mesh (previously created in writeGeometry) to which <node> corresponds exists, + * link Object with that mesh. + * + * Update: since we cannot link a Mesh with Object in + * writeGeometry because <geometry> does not reference <node>, + * we link Objects with Meshes here. + */ + vscenes.push_back(visualScene); + + return true; +} + +/** + * When this method is called, the writer must handle all nodes contained in the + * library nodes. + * \return The writer should return true, if writing succeeded, false otherwise. + */ +bool DocumentImporter::writeLibraryNodes(const COLLADAFW::LibraryNodes *libraryNodes) +{ + if (mImportStage == Fetching_Controller_data) { + return true; + } + + Scene *sce = CTX_data_scene(mContext); + + const COLLADAFW::NodePointerArray &nodes = libraryNodes->getNodes(); + + fprintf(stderr, "+-- Read Library nodes ----------\n"); + for (unsigned int i = 0; i < nodes.getCount(); i++) { + std::vector<Object *> *child_objects; + child_objects = write_node(nodes[i], NULL, sce, NULL, true); + delete child_objects; + } + return true; +} + +/** + * When this method is called, the writer must write the geometry. + * \return The writer should return true, if writing succeeded, false otherwise. + */ +bool DocumentImporter::writeGeometry(const COLLADAFW::Geometry *geom) +{ + if (mImportStage == Fetching_Controller_data) { + return true; + } + + return mesh_importer.write_geometry(geom); +} + +/** + * When this method is called, the writer must write the material. + * \return The writer should return true, if writing succeeded, false otherwise. + */ +bool DocumentImporter::writeMaterial(const COLLADAFW::Material *cmat) +{ + if (mImportStage == Fetching_Controller_data) { + return true; + } + + Main *bmain = CTX_data_main(mContext); + const std::string &str_mat_id = cmat->getName().size() ? cmat->getName() : cmat->getOriginalId(); + Material *ma = BKE_material_add(bmain, (char *)str_mat_id.c_str()); + + this->uid_effect_map[cmat->getInstantiatedEffect()] = ma; + this->uid_material_map[cmat->getUniqueId()] = ma; + + return true; +} + +void DocumentImporter::write_profile_COMMON(COLLADAFW::EffectCommon *ef, Material *ma) +{ + MaterialNode matNode = MaterialNode(mContext, ef, ma, uid_image_map); + + /* Direct mapping to principled BSDF Shader */ + matNode.set_diffuse(ef->getDiffuse()); + matNode.set_emission(ef->getEmission()); + matNode.set_ior(ef->getIndexOfRefraction()); + matNode.set_alpha(ef->getOpaqueMode(), ef->getTransparent(), ef->getTransparency()); + + /* following mapping still needs to be verified */ +#if 0 + // needs rework to be done for 2.81 + matNode.set_shininess(ef->getShininess()); +#endif + matNode.set_reflectivity(ef->getReflectivity()); + + /* not supported by principled BSDF */ + matNode.set_ambient(ef->getAmbient()); + matNode.set_specular(ef->getSpecular()); + matNode.set_reflective(ef->getReflective()); +} + +/** + * When this method is called, the writer must write the effect. + * \return The writer should return true, if writing succeeded, false otherwise. + */ +bool DocumentImporter::writeEffect(const COLLADAFW::Effect *effect) +{ + if (mImportStage == Fetching_Controller_data) { + return true; + } + + const COLLADAFW::UniqueId &uid = effect->getUniqueId(); + + if (uid_effect_map.find(uid) == uid_effect_map.end()) { + fprintf(stderr, "Couldn't find a material by UID.\n"); + return true; + } + + Material *ma = uid_effect_map[uid]; + std::map<COLLADAFW::UniqueId, Material *>::iterator iter; + for (iter = uid_material_map.begin(); iter != uid_material_map.end(); iter++) { + if (iter->second == ma) { + this->FW_object_map[iter->first] = effect; + break; + } + } + COLLADAFW::CommonEffectPointerArray common_efs = effect->getCommonEffects(); + if (common_efs.getCount() < 1) { + fprintf(stderr, "Couldn't find <profile_COMMON>.\n"); + return true; + } + /* XXX TODO: Take all <profile_common>s + * Currently only first <profile_common> is supported */ + COLLADAFW::EffectCommon *ef = common_efs[0]; + write_profile_COMMON(ef, ma); + this->FW_object_map[effect->getUniqueId()] = effect; + + return true; +} + +/** + * When this method is called, the writer must write the camera. + * \return The writer should return true, if writing succeeded, false otherwise. + */ +bool DocumentImporter::writeCamera(const COLLADAFW::Camera *camera) +{ + if (mImportStage == Fetching_Controller_data) { + return true; + } + + Main *bmain = CTX_data_main(mContext); + Camera *cam = NULL; + std::string cam_id, cam_name; + + ExtraTags *et = getExtraTags(camera->getUniqueId()); + cam_id = camera->getOriginalId(); + cam_name = camera->getName(); + if (cam_name.size()) { + cam = (Camera *)BKE_camera_add(bmain, (char *)cam_name.c_str()); + } + else { + cam = (Camera *)BKE_camera_add(bmain, (char *)cam_id.c_str()); + } + + if (!cam) { + fprintf(stderr, "Cannot create camera.\n"); + return true; + } + + if (et && et->isProfile("blender")) { + et->setData("shiftx", &(cam->shiftx)); + et->setData("shifty", &(cam->shifty)); + et->setData("dof_distance", &(cam->dof.focus_distance)); + } + cam->clip_start = camera->getNearClippingPlane().getValue(); + cam->clip_end = camera->getFarClippingPlane().getValue(); + + COLLADAFW::Camera::CameraType type = camera->getCameraType(); + switch (type) { + case COLLADAFW::Camera::ORTHOGRAPHIC: { + cam->type = CAM_ORTHO; + } break; + case COLLADAFW::Camera::PERSPECTIVE: { + cam->type = CAM_PERSP; + } break; + case COLLADAFW::Camera::UNDEFINED_CAMERATYPE: { + fprintf(stderr, "Current camera type is not supported.\n"); + cam->type = CAM_PERSP; + } break; + } + + switch (camera->getDescriptionType()) { + case COLLADAFW::Camera::ASPECTRATIO_AND_Y: { + switch (cam->type) { + case CAM_ORTHO: { + double ymag = 2 * camera->getYMag().getValue(); + double aspect = camera->getAspectRatio().getValue(); + double xmag = aspect * ymag; + cam->ortho_scale = (float)xmag; + } break; + case CAM_PERSP: + default: { + double yfov = camera->getYFov().getValue(); + double aspect = camera->getAspectRatio().getValue(); + + /* NOTE: Needs more testing (As we currently have no official test data for this) */ + + double xfov = 2.0f * atanf(aspect * tanf(DEG2RADF(yfov) * 0.5f)); + cam->lens = fov_to_focallength(xfov, cam->sensor_x); + } break; + } + } break; + /* XXX correct way to do following four is probably to get also render + * size and determine proper settings from that somehow */ + case COLLADAFW::Camera::ASPECTRATIO_AND_X: + case COLLADAFW::Camera::SINGLE_X: + case COLLADAFW::Camera::X_AND_Y: { + switch (cam->type) { + case CAM_ORTHO: + cam->ortho_scale = (float)camera->getXMag().getValue() * 2; + break; + case CAM_PERSP: + default: { + double x = camera->getXFov().getValue(); + /* x is in degrees, cam->lens is in millimiters */ + cam->lens = fov_to_focallength(DEG2RADF(x), cam->sensor_x); + } break; + } + } break; + case COLLADAFW::Camera::SINGLE_Y: { + switch (cam->type) { + case CAM_ORTHO: + cam->ortho_scale = (float)camera->getYMag().getValue(); + break; + case CAM_PERSP: + default: { + double yfov = camera->getYFov().getValue(); + /* yfov is in degrees, cam->lens is in millimiters */ + cam->lens = fov_to_focallength(DEG2RADF(yfov), cam->sensor_x); + } break; + } + } break; + case COLLADAFW::Camera::UNDEFINED: + /* read nothing, use blender defaults. */ + break; + } + + this->uid_camera_map[camera->getUniqueId()] = cam; + this->FW_object_map[camera->getUniqueId()] = camera; + /* XXX import camera options */ + return true; +} + +/** + * When this method is called, the writer must write the image. + * \return The writer should return true, if writing succeeded, false otherwise. + */ +bool DocumentImporter::writeImage(const COLLADAFW::Image *image) +{ + if (mImportStage == Fetching_Controller_data) { + return true; + } + + const std::string &imagepath = image->getImageURI().toNativePath(); + + char dir[FILE_MAX]; + char absolute_path[FILE_MAX]; + const char *workpath; + + BLI_split_dir_part(this->import_settings->filepath, dir, sizeof(dir)); + BLI_join_dirfile(absolute_path, sizeof(absolute_path), dir, imagepath.c_str()); + if (BLI_exists(absolute_path)) { + workpath = absolute_path; + } + else { + /* Maybe imagepath was already absolute ? */ + if (!BLI_exists(imagepath.c_str())) { + fprintf(stderr, "|! Image not found: %s\n", imagepath.c_str()); + return true; + } + workpath = imagepath.c_str(); + } + + Image *ima = BKE_image_load_exists(CTX_data_main(mContext), workpath); + if (!ima) { + fprintf(stderr, "|! Cannot create image: %s\n", workpath); + return true; + } + this->uid_image_map[image->getUniqueId()] = ima; + fprintf(stderr, "| import Image: %s\n", workpath); + return true; +} + +/** + * When this method is called, the writer must write the light. + * \return The writer should return true, if writing succeeded, false otherwise. + */ +bool DocumentImporter::writeLight(const COLLADAFW::Light *light) +{ + if (mImportStage == Fetching_Controller_data) { + return true; + } + + Main *bmain = CTX_data_main(mContext); + Light *lamp = NULL; + std::string la_id, la_name; + + ExtraTags *et = getExtraTags(light->getUniqueId()); +#if 0 + TagsMap::iterator etit; + ExtraTags *et = 0; + etit = uid_tags_map.find(light->getUniqueId().toAscii()); + if (etit != uid_tags_map.end()) { + et = etit->second; + } +#endif + + la_id = light->getOriginalId(); + la_name = light->getName(); + if (la_name.size()) { + lamp = (Light *)BKE_light_add(bmain, (char *)la_name.c_str()); + } + else { + lamp = (Light *)BKE_light_add(bmain, (char *)la_id.c_str()); + } + + if (!lamp) { + fprintf(stderr, "Cannot create light.\n"); + return true; + } + + /* if we find an ExtraTags for this, use that instead. */ + if (et && et->isProfile("blender")) { + et->setData("type", &(lamp->type)); + et->setData("flag", &(lamp->flag)); + et->setData("mode", &(lamp->mode)); + et->setData("gamma", &(lamp->k)); + et->setData("red", &(lamp->r)); + et->setData("green", &(lamp->g)); + et->setData("blue", &(lamp->b)); + et->setData("shadow_r", &(lamp->shdwr)); + et->setData("shadow_g", &(lamp->shdwg)); + et->setData("shadow_b", &(lamp->shdwb)); + et->setData("energy", &(lamp->energy)); + et->setData("dist", &(lamp->dist)); + et->setData("spotsize", &(lamp->spotsize)); + lamp->spotsize = DEG2RADF(lamp->spotsize); + et->setData("spotblend", &(lamp->spotblend)); + et->setData("att1", &(lamp->att1)); + et->setData("att2", &(lamp->att2)); + et->setData("falloff_type", &(lamp->falloff_type)); + et->setData("clipsta", &(lamp->clipsta)); + et->setData("clipend", &(lamp->clipend)); + et->setData("bias", &(lamp->bias)); + et->setData("soft", &(lamp->soft)); + et->setData("bufsize", &(lamp->bufsize)); + et->setData("buffers", &(lamp->buffers)); + et->setData("area_shape", &(lamp->area_shape)); + et->setData("area_size", &(lamp->area_size)); + et->setData("area_sizey", &(lamp->area_sizey)); + et->setData("area_sizez", &(lamp->area_sizez)); + } + else { + float constatt = light->getConstantAttenuation().getValue(); + float linatt = light->getLinearAttenuation().getValue(); + float quadatt = light->getQuadraticAttenuation().getValue(); + float d = 25.0f; + float att1 = 0.0f; + float att2 = 0.0f; + float e = 1.0f; + + if (light->getColor().isValid()) { + COLLADAFW::Color col = light->getColor(); + lamp->r = col.getRed(); + lamp->g = col.getGreen(); + lamp->b = col.getBlue(); + } + + if (IS_EQ(linatt, 0.0f) && quadatt > 0.0f) { + att2 = quadatt; + d = sqrt(1.0f / quadatt); + } + /* linear light */ + else if (IS_EQ(quadatt, 0.0f) && linatt > 0.0f) { + att1 = linatt; + d = (1.0f / linatt); + } + else if (IS_EQ(constatt, 1.0f)) { + att1 = 1.0f; + } + else { + /* assuming point light (const att = 1.0); */ + att1 = 1.0f; + } + + d *= (1.0f / unit_converter.getLinearMeter()); + + lamp->energy = e; + lamp->dist = d; + + switch (light->getLightType()) { + case COLLADAFW::Light::AMBIENT_LIGHT: { + lamp->type = LA_SUN; // TODO needs more thoughts + } break; + case COLLADAFW::Light::SPOT_LIGHT: { + lamp->type = LA_SPOT; + lamp->att1 = att1; + lamp->att2 = att2; + if (IS_EQ(att1, 0.0f) && att2 > 0) { + lamp->falloff_type = LA_FALLOFF_INVSQUARE; + } + if (IS_EQ(att2, 0.0f) && att1 > 0) { + lamp->falloff_type = LA_FALLOFF_INVLINEAR; + } + lamp->spotsize = DEG2RADF(light->getFallOffAngle().getValue()); + lamp->spotblend = light->getFallOffExponent().getValue(); + } break; + case COLLADAFW::Light::DIRECTIONAL_LIGHT: { + /* our sun is very strong, so pick a smaller energy level */ + lamp->type = LA_SUN; + } break; + case COLLADAFW::Light::POINT_LIGHT: { + lamp->type = LA_LOCAL; + lamp->att1 = att1; + lamp->att2 = att2; + if (IS_EQ(att1, 0.0f) && att2 > 0) { + lamp->falloff_type = LA_FALLOFF_INVSQUARE; + } + if (IS_EQ(att2, 0.0f) && att1 > 0) { + lamp->falloff_type = LA_FALLOFF_INVLINEAR; + } + } break; + case COLLADAFW::Light::UNDEFINED: { + fprintf(stderr, "Current light type is not supported.\n"); + lamp->type = LA_LOCAL; + } break; + } + } + + this->uid_light_map[light->getUniqueId()] = lamp; + this->FW_object_map[light->getUniqueId()] = light; + return true; +} + +/* this function is called only for animations that pass COLLADAFW::validate */ +bool DocumentImporter::writeAnimation(const COLLADAFW::Animation *anim) +{ + if (mImportStage == Fetching_Controller_data) { + return true; + } + + return anim_importer.write_animation(anim); +} + +/* called on post-process stage after writeVisualScenes */ +bool DocumentImporter::writeAnimationList(const COLLADAFW::AnimationList *animationList) +{ + if (mImportStage == Fetching_Controller_data) { + return true; + } + + /* return true; */ + return anim_importer.write_animation_list(animationList); +} + +#if WITH_OPENCOLLADA_ANIMATION_CLIP +/* Since opencollada 1.6.68 + * called on post-process stage after writeVisualScenes */ +bool DocumentImporter::writeAnimationClip(const COLLADAFW::AnimationClip *animationClip) +{ + if (mImportStage == Fetching_Controller_data) { + return true; + } + + return true; + /* TODO: implement import of AnimationClips */ + // return animation_clip_importer.write_animation_clip(animationClip); +} +#endif + +/** + * When this method is called, the writer must write the skin controller data. + * \return The writer should return true, if writing succeeded, false otherwise. + */ +bool DocumentImporter::writeSkinControllerData(const COLLADAFW::SkinControllerData *skin) +{ + return armature_importer.write_skin_controller_data(skin); +} + +/* this is called on postprocess, before writeVisualScenes */ +bool DocumentImporter::writeController(const COLLADAFW::Controller *controller) +{ + if (mImportStage == Fetching_Controller_data) { + return true; + } + + return armature_importer.write_controller(controller); +} + +bool DocumentImporter::writeFormulas(const COLLADAFW::Formulas *formulas) +{ + return true; +} + +bool DocumentImporter::writeKinematicsScene(const COLLADAFW::KinematicsScene *kinematicsScene) +{ + return true; +} + +ExtraTags *DocumentImporter::getExtraTags(const COLLADAFW::UniqueId &uid) +{ + if (uid_tags_map.find(uid.toAscii()) == uid_tags_map.end()) { + return NULL; + } + return uid_tags_map[uid.toAscii()]; +} + +bool DocumentImporter::addExtraTags(const COLLADAFW::UniqueId &uid, ExtraTags *extra_tags) +{ + uid_tags_map[uid.toAscii()] = extra_tags; + return true; +} + +bool DocumentImporter::is_armature(COLLADAFW::Node *node) +{ + COLLADAFW::NodePointerArray &child_nodes = node->getChildNodes(); + for (unsigned int i = 0; i < child_nodes.getCount(); i++) { + if (child_nodes[i]->getType() == COLLADAFW::Node::JOINT) { + return true; + } + else { + continue; + } + } + + /* no child is JOINT */ + return false; +} diff --git a/source/blender/io/collada/DocumentImporter.h b/source/blender/io/collada/DocumentImporter.h new file mode 100644 index 00000000000..e47c844f7c6 --- /dev/null +++ b/source/blender/io/collada/DocumentImporter.h @@ -0,0 +1,172 @@ +/* + * 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 collada + */ + +#ifndef __DOCUMENTIMPORTER_H__ +#define __DOCUMENTIMPORTER_H__ + +#include "COLLADAFWIWriter.h" +#include "COLLADAFWMaterial.h" +#include "COLLADAFWEffect.h" +#include "COLLADAFWColor.h" +#include "COLLADAFWImage.h" +#include "COLLADAFWInstanceGeometry.h" +#include "COLLADAFWController.h" +#include "COLLADAFWMorphController.h" +#include "COLLADAFWSkinController.h" +#include "COLLADAFWEffectCommon.h" + +#include "BKE_object.h" +#include "BKE_constraint.h" + +#include "TransformReader.h" +#include "AnimationImporter.h" +#include "ArmatureImporter.h" +#include "ControllerExporter.h" +#include "MeshImporter.h" +#include "ImportSettings.h" + +struct bContext; + +/** Importer class. */ +class DocumentImporter : COLLADAFW::IWriter { + public: + //! Enumeration to denote the stage of import + enum ImportStage { + Fetching_Scene_data, /* First pass to collect all data except controller */ + Fetching_Controller_data, /* Second pass to collect controller data */ + }; + /** Constructor */ + DocumentImporter(bContext *C, const ImportSettings *import_settings); + + /** Destructor */ + ~DocumentImporter(); + + /** Function called by blender UI */ + bool import(); + + /** these should not be here */ + Object *create_camera_object(COLLADAFW::InstanceCamera *, Scene *); + Object *create_light_object(COLLADAFW::InstanceLight *, Scene *); + Object *create_instance_node(Object *, COLLADAFW::Node *, COLLADAFW::Node *, Scene *, bool); + void create_constraints(ExtraTags *et, Object *ob); + std::vector<Object *> *write_node(COLLADAFW::Node *, COLLADAFW::Node *, Scene *, Object *, bool); + void write_profile_COMMON(COLLADAFW::EffectCommon *, Material *); + + void translate_anim_recursive(COLLADAFW::Node *, COLLADAFW::Node *, Object *); + + /** + * This method will be called if an error in the loading process occurred and the loader cannot + * continue to load. The writer should undo all operations that have been performed. + * \param errorMessage: A message containing information about the error that occurred. + */ + void cancel(const COLLADAFW::String &errorMessage); + + /** This is the method called. The writer hast to prepare to receive data.*/ + void start(); + + /** This method is called after the last write* method. No other methods will be called after + * this.*/ + void finish(); + + bool writeGlobalAsset(const COLLADAFW::FileInfo *); + std::string get_import_version(const COLLADAFW::FileInfo *asset); + + bool writeScene(const COLLADAFW::Scene *); + + bool writeVisualScene(const COLLADAFW::VisualScene *); + + bool writeLibraryNodes(const COLLADAFW::LibraryNodes *); + + bool writeAnimation(const COLLADAFW::Animation *); + + bool writeAnimationList(const COLLADAFW::AnimationList *); + +#if WITH_OPENCOLLADA_ANIMATION_CLIP + // Please enable this when building with Collada 1.6.65 or newer (also in DocumentImporter.cpp) + bool writeAnimationClip(const COLLADAFW::AnimationClip *animationClip); +#endif + + bool writeGeometry(const COLLADAFW::Geometry *); + + bool writeMaterial(const COLLADAFW::Material *); + + bool writeEffect(const COLLADAFW::Effect *); + + bool writeCamera(const COLLADAFW::Camera *); + + bool writeImage(const COLLADAFW::Image *); + + bool writeLight(const COLLADAFW::Light *); + + bool writeSkinControllerData(const COLLADAFW::SkinControllerData *); + + bool writeController(const COLLADAFW::Controller *); + + bool writeFormulas(const COLLADAFW::Formulas *); + + bool writeKinematicsScene(const COLLADAFW::KinematicsScene *); + + /** Add element and data for UniqueId */ + bool addExtraTags(const COLLADAFW::UniqueId &uid, ExtraTags *extra_tags); + /** Get an extisting ExtraTags for uid */ + ExtraTags *getExtraTags(const COLLADAFW::UniqueId &uid); + + bool is_armature(COLLADAFW::Node *node); + + private: + const ImportSettings *import_settings; + + /** Current import stage we're in. */ + ImportStage mImportStage; + + bContext *mContext; + ViewLayer *view_layer; + + UnitConverter unit_converter; + ArmatureImporter armature_importer; + MeshImporter mesh_importer; + AnimationImporter anim_importer; + + /** TagsMap typedef for uid_tags_map. */ + typedef std::map<std::string, ExtraTags *> TagsMap; + /** Tags map of unique id as a string and ExtraTags instance. */ + TagsMap uid_tags_map; + + UidImageMap uid_image_map; + std::map<COLLADAFW::UniqueId, Material *> uid_material_map; + std::map<COLLADAFW::UniqueId, Material *> uid_effect_map; + std::map<COLLADAFW::UniqueId, Camera *> uid_camera_map; + std::map<COLLADAFW::UniqueId, Light *> uid_light_map; + std::map<Material *, TexIndexTextureArrayMap> material_texture_mapping_map; + std::multimap<COLLADAFW::UniqueId, Object *> object_map; + std::map<COLLADAFW::UniqueId, COLLADAFW::Node *> node_map; + std::vector<const COLLADAFW::VisualScene *> vscenes; + std::vector<Object *> libnode_ob; + + std::map<COLLADAFW::UniqueId, COLLADAFW::Node *> + root_map; // find root joint by child joint uid, for bone tree evaluation during resampling + std::map<COLLADAFW::UniqueId, const COLLADAFW::Object *> FW_object_map; + + std::string import_from_version; + + void report_unknown_reference(const COLLADAFW::Node &node, const std::string object_type); +}; + +#endif diff --git a/source/blender/io/collada/EffectExporter.cpp b/source/blender/io/collada/EffectExporter.cpp new file mode 100644 index 00000000000..a1174fdff56 --- /dev/null +++ b/source/blender/io/collada/EffectExporter.cpp @@ -0,0 +1,312 @@ +/* + * 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 collada + */ + +#include <map> +#include <set> + +#include "COLLADASWEffectProfile.h" +#include "COLLADAFWColorOrTexture.h" + +#include "EffectExporter.h" +#include "DocumentExporter.h" +#include "MaterialExporter.h" + +#include "collada_internal.h" +#include "collada_utils.h" + +extern "C" { +#include "DNA_mesh_types.h" +#include "DNA_world_types.h" + +#include "BKE_collection.h" +#include "BKE_customdata.h" +#include "BKE_mesh.h" +#include "BKE_material.h" +} + +static std::string getActiveUVLayerName(Object *ob) +{ + Mesh *me = (Mesh *)ob->data; + + int num_layers = CustomData_number_of_layers(&me->ldata, CD_MLOOPUV); + if (num_layers) { + return std::string(bc_CustomData_get_active_layer_name(&me->ldata, CD_MLOOPUV)); + } + + return ""; +} + +EffectsExporter::EffectsExporter(COLLADASW::StreamWriter *sw, + BCExportSettings &export_settings, + KeyImageMap &key_image_map) + : COLLADASW::LibraryEffects(sw), export_settings(export_settings), key_image_map(key_image_map) +{ +} + +bool EffectsExporter::hasEffects(Scene *sce) +{ + FOREACH_SCENE_OBJECT_BEGIN (sce, ob) { + int a; + for (a = 0; a < ob->totcol; a++) { + Material *ma = BKE_object_material_get(ob, a + 1); + + // no material, but check all of the slots + if (!ma) { + continue; + } + + return true; + } + } + FOREACH_SCENE_OBJECT_END; + return false; +} + +void EffectsExporter::exportEffects(bContext *C, Scene *sce) +{ + if (hasEffects(sce)) { + this->mContext = C; + this->scene = sce; + openLibrary(); + MaterialFunctor mf; + mf.forEachMaterialInExportSet<EffectsExporter>( + sce, *this, this->export_settings.get_export_set()); + + closeLibrary(); + } +} + +void EffectsExporter::set_shader_type(COLLADASW::EffectProfile &ep, Material *ma) +{ + /* XXX check if BLINN and PHONG can be supported as well */ + ep.setShaderType(COLLADASW::EffectProfile::LAMBERT); +} + +void EffectsExporter::set_transparency(COLLADASW::EffectProfile &ep, Material *ma) +{ + double alpha = bc_get_alpha(ma); + if (alpha < 1) { + // workaround use <transparent> to avoid wrong handling of <transparency> by other tools + COLLADASW::ColorOrTexture cot = bc_get_cot(0, 0, 0, alpha); + ep.setTransparent(cot, false, "alpha"); + ep.setOpaque(COLLADASW::EffectProfile::A_ONE); + } +} + +void EffectsExporter::set_diffuse_color(COLLADASW::EffectProfile &ep, Material *ma) +{ + COLLADASW::ColorOrTexture cot = bc_get_base_color(ma); + ep.setDiffuse(cot, false, "diffuse"); +} + +void EffectsExporter::set_ambient(COLLADASW::EffectProfile &ep, Material *ma) +{ + COLLADASW::ColorOrTexture cot = bc_get_ambient(ma); + ep.setAmbient(cot, false, "ambient"); +} +void EffectsExporter::set_specular(COLLADASW::EffectProfile &ep, Material *ma) +{ + COLLADASW::ColorOrTexture cot = bc_get_specular(ma); + ep.setSpecular(cot, false, "specular"); +} +void EffectsExporter::set_reflective(COLLADASW::EffectProfile &ep, Material *ma) +{ + COLLADASW::ColorOrTexture cot = bc_get_reflective(ma); + ep.setReflective(cot, false, "reflective"); +} + +void EffectsExporter::set_reflectivity(COLLADASW::EffectProfile &ep, Material *ma) +{ + double reflectivity = bc_get_reflectivity(ma); + if (reflectivity > 0.0) { + ep.setReflectivity(reflectivity, false, "specular"); + } +} + +void EffectsExporter::set_emission(COLLADASW::EffectProfile &ep, Material *ma) +{ + COLLADASW::ColorOrTexture cot = bc_get_emission(ma); + ep.setEmission(cot, false, "emission"); +} + +void EffectsExporter::set_ior(COLLADASW::EffectProfile &ep, Material *ma) +{ + double alpha = bc_get_ior(ma); + ep.setIndexOfRefraction(alpha, false, "ior"); +} + +void EffectsExporter::set_shininess(COLLADASW::EffectProfile &ep, Material *ma) +{ + double shininess = bc_get_shininess(ma); + ep.setShininess(shininess, false, "shininess"); +} + +void EffectsExporter::get_images(Material *ma, KeyImageMap &material_image_map) +{ + if (!ma->use_nodes) { + return; + } + + MaterialNode material = MaterialNode(mContext, ma, key_image_map); + Image *image = material.get_diffuse_image(); + if (image == nullptr) { + return; + } + + std::string uid(id_name(image)); + std::string key = translate_id(uid); + + if (material_image_map.find(key) == material_image_map.end()) { + material_image_map[key] = image; + key_image_map[key] = image; + } +} + +void EffectsExporter::create_image_samplers(COLLADASW::EffectProfile &ep, + KeyImageMap &material_image_map, + std::string &active_uv) +{ + KeyImageMap::iterator iter; + + for (iter = material_image_map.begin(); iter != material_image_map.end(); iter++) { + + Image *image = iter->second; + std::string uid(id_name(image)); + std::string key = translate_id(uid); + + COLLADASW::Sampler *sampler = new COLLADASW::Sampler( + COLLADASW::Sampler::SAMPLER_TYPE_2D, + key + COLLADASW::Sampler::SAMPLER_SID_SUFFIX, + key + COLLADASW::Sampler::SURFACE_SID_SUFFIX); + + sampler->setImageId(key); + + ep.setDiffuse(createTexture(image, active_uv, sampler), false, "diffuse"); + } +} + +void EffectsExporter::operator()(Material *ma, Object *ob) +{ + KeyImageMap material_image_map; + + openEffect(get_effect_id(ma)); + + COLLADASW::EffectProfile ep(mSW); + ep.setProfileType(COLLADASW::EffectProfile::COMMON); + ep.openProfile(); + set_shader_type(ep, ma); // creates a Lambert Shader for now + + COLLADASW::ColorOrTexture cot; + + set_diffuse_color(ep, ma); + set_emission(ep, ma); + set_ior(ep, ma); + set_reflectivity(ep, ma); + set_transparency(ep, ma); + + /* TODO: */ + // set_shininess(ep, ma); shininess not supported for lambert + // set_ambient(ep, ma); + // set_specular(ep, ma); + + get_images(ma, material_image_map); + std::string active_uv(getActiveUVLayerName(ob)); + create_image_samplers(ep, material_image_map, active_uv); + +#if 0 + unsigned int a, b; + for (a = 0, b = 0; a < tex_indices.size(); a++) { + MTex *t = ma->mtex[tex_indices[a]]; + Image *ima = t->tex->ima; + + // Image not set for texture + if (!ima) { + continue; + } + + std::string key(id_name(ima)); + key = translate_id(key); + + // create only one <sampler>/<surface> pair for each unique image + if (im_samp_map.find(key) == im_samp_map.end()) { + //<newparam> <sampler> <source> + COLLADASW::Sampler sampler(COLLADASW::Sampler::SAMPLER_TYPE_2D, + key + COLLADASW::Sampler::SAMPLER_SID_SUFFIX, + key + COLLADASW::Sampler::SURFACE_SID_SUFFIX); + sampler.setImageId(key); + // copy values to arrays since they will live longer + samplers[a] = sampler; + + // store pointers so they can be used later when we create <texture>s + samp_surf[b] = &samplers[a]; + //samp_surf[b][1] = &surfaces[a]; + + im_samp_map[key] = b; + b++; + } + } + + for (a = 0; a < tex_indices.size(); a++) { + MTex *t = ma->mtex[tex_indices[a]]; + Image *ima = t->tex->ima; + + if (!ima) { + continue; + } + + std::string key(id_name(ima)); + key = translate_id(key); + int i = im_samp_map[key]; + std::string uvname = strlen(t->uvname) ? t->uvname : active_uv; + COLLADASW::Sampler *sampler = (COLLADASW::Sampler *) + samp_surf[i]; // possibly uninitialized memory ... + writeTextures(ep, key, sampler, t, ima, uvname); + } +#endif + + // performs the actual writing + ep.addProfileElements(); + ep.addExtraTechniques(mSW); + + ep.closeProfile(); + closeEffect(); +} + +COLLADASW::ColorOrTexture EffectsExporter::createTexture(Image *ima, + std::string &uv_layer_name, + COLLADASW::Sampler *sampler + /*COLLADASW::Surface *surface*/) +{ + + COLLADASW::Texture texture(translate_id(id_name(ima))); + texture.setTexcoord(uv_layer_name); + // texture.setSurface(*surface); + texture.setSampler(*sampler); + + COLLADASW::ColorOrTexture cot(texture); + return cot; +} + +COLLADASW::ColorOrTexture EffectsExporter::getcol(float r, float g, float b, float a) +{ + COLLADASW::Color color(r, g, b, a); + COLLADASW::ColorOrTexture cot(color); + return cot; +} diff --git a/source/blender/io/collada/EffectExporter.h b/source/blender/io/collada/EffectExporter.h new file mode 100644 index 00000000000..57df844233c --- /dev/null +++ b/source/blender/io/collada/EffectExporter.h @@ -0,0 +1,89 @@ +/* + * 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 collada + */ + +#ifndef __EFFECTEXPORTER_H__ +#define __EFFECTEXPORTER_H__ + +#include <string> +#include <vector> + +#include "COLLADASWColorOrTexture.h" +#include "COLLADASWStreamWriter.h" +#include "COLLADASWSampler.h" +#include "COLLADASWLibraryEffects.h" + +#include "DNA_image_types.h" +#include "DNA_material_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "ExportSettings.h" +#include "collada_utils.h" + +class EffectsExporter : COLLADASW::LibraryEffects { + public: + EffectsExporter(COLLADASW::StreamWriter *sw, + BCExportSettings &export_settings, + KeyImageMap &key_image_map); + void exportEffects(bContext *C, Scene *sce); + + void operator()(Material *ma, Object *ob); + + COLLADASW::ColorOrTexture createTexture(Image *ima, + std::string &uv_layer_name, + COLLADASW::Sampler *sampler + /*COLLADASW::Surface *surface*/); + + COLLADASW::ColorOrTexture getcol(float r, float g, float b, float a); + + private: + void set_shader_type(COLLADASW::EffectProfile &ep, Material *ma); + + void set_diffuse_color(COLLADASW::EffectProfile &ep, Material *ma); + void set_emission(COLLADASW::EffectProfile &ep, Material *ma); + void set_ior(COLLADASW::EffectProfile &ep, Material *ma); + void set_shininess(COLLADASW::EffectProfile &ep, Material *ma); + void set_reflectivity(COLLADASW::EffectProfile &ep, Material *ma); + void set_transparency(COLLADASW::EffectProfile &ep, Material *ma); + void set_ambient(COLLADASW::EffectProfile &ep, Material *ma); + void set_specular(COLLADASW::EffectProfile &ep, Material *ma); + void set_reflective(COLLADASW::EffectProfile &ep, Material *ma); + + void get_images(Material *ma, KeyImageMap &uid_image_map); + void create_image_samplers(COLLADASW::EffectProfile &ep, + KeyImageMap &uid_image_map, + std::string &active_uv); + + void writeTextures(COLLADASW::EffectProfile &ep, + std::string &key, + COLLADASW::Sampler *sampler, + MTex *t, + Image *ima, + std::string &uvname); + + bool hasEffects(Scene *sce); + + BCExportSettings &export_settings; + KeyImageMap &key_image_map; + Scene *scene; + bContext *mContext; +}; + +#endif diff --git a/source/blender/io/collada/ErrorHandler.cpp b/source/blender/io/collada/ErrorHandler.cpp new file mode 100644 index 00000000000..286bcbfb759 --- /dev/null +++ b/source/blender/io/collada/ErrorHandler.cpp @@ -0,0 +1,118 @@ +/* + * 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 collada + */ +#include "ErrorHandler.h" +#include <iostream> + +#include "COLLADASaxFWLIError.h" +#include "COLLADASaxFWLSaxParserError.h" +#include "COLLADASaxFWLSaxFWLError.h" + +#include "GeneratedSaxParserParserError.h" + +#include <string.h> + +#include "BLI_utildefines.h" + +//-------------------------------------------------------------------- +ErrorHandler::ErrorHandler() : mError(false) +{ +} + +//-------------------------------------------------------------------- +ErrorHandler::~ErrorHandler() +{ +} + +//-------------------------------------------------------------------- +bool ErrorHandler::handleError(const COLLADASaxFWL::IError *error) +{ + /* This method must return false when Collada should continue. + * See https://github.com/KhronosGroup/OpenCOLLADA/issues/442 + */ + bool isError = true; + std::string error_context; + std::string error_message; + + if (error->getErrorClass() == COLLADASaxFWL::IError::ERROR_SAXPARSER) { + error_context = "Schema validation"; + + COLLADASaxFWL::SaxParserError *saxParserError = (COLLADASaxFWL::SaxParserError *)error; + const GeneratedSaxParser::ParserError &parserError = saxParserError->getError(); + error_message = parserError.getErrorMessage(); + + if (parserError.getErrorType() == + GeneratedSaxParser::ParserError::ERROR_VALIDATION_MIN_OCCURS_UNMATCHED) { + if (STREQ(parserError.getElement(), "effect")) { + isError = false; + } + } + + else if (parserError.getErrorType() == + GeneratedSaxParser::ParserError:: + ERROR_VALIDATION_SEQUENCE_PREVIOUS_SIBLING_NOT_PRESENT) { + if (!(STREQ(parserError.getElement(), "extra") && + STREQ(parserError.getAdditionalText().c_str(), "sibling: fx_profile_abstract"))) { + isError = false; + } + } + + else if (parserError.getErrorType() == + GeneratedSaxParser::ParserError::ERROR_COULD_NOT_OPEN_FILE) { + isError = true; + error_context = "File access"; + } + + else if (parserError.getErrorType() == + GeneratedSaxParser::ParserError::ERROR_REQUIRED_ATTRIBUTE_MISSING) { + isError = true; + } + + else { + isError = (parserError.getSeverity() != + GeneratedSaxParser::ParserError::Severity::SEVERITY_ERROR_NONCRITICAL); + } + } + else if (error->getErrorClass() == COLLADASaxFWL::IError::ERROR_SAXFWL) { + error_context = "Sax FWL"; + COLLADASaxFWL::SaxFWLError *saxFWLError = (COLLADASaxFWL::SaxFWLError *)error; + error_message = saxFWLError->getErrorMessage(); + + /* + * Accept non critical errors as warnings (i.e. texture not found) + * This makes the importer more graceful, so it now imports what makes sense. + */ + + isError = (saxFWLError->getSeverity() != COLLADASaxFWL::IError::SEVERITY_ERROR_NONCRITICAL); + } + else { + error_context = "OpenCollada"; + error_message = error->getFullErrorMessage(); + isError = true; + } + + std::string severity = (isError) ? "Error" : "Warning"; + std::cout << error_context << " (" << severity << "): " << error_message << std::endl; + if (isError) { + std::cout << "The Collada import has been forced to stop." << std::endl; + std::cout << "Please fix the reported error and then try again."; + mError = true; + } + return isError; +} diff --git a/source/blender/io/collada/ErrorHandler.h b/source/blender/io/collada/ErrorHandler.h new file mode 100644 index 00000000000..f040855244d --- /dev/null +++ b/source/blender/io/collada/ErrorHandler.h @@ -0,0 +1,57 @@ +/* + * 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 collada + */ + +#ifndef __ERRORHANDLER_H__ +#define __ERRORHANDLER_H__ + +#include <string> +#include <map> +#include <vector> +#include <algorithm> // sort() + +#include "COLLADASaxFWLIErrorHandler.h" + +/** \brief Handler class for parser errors + */ +class ErrorHandler : public COLLADASaxFWL::IErrorHandler { + public: + /** Constructor. */ + ErrorHandler(); + + /** Destructor. */ + virtual ~ErrorHandler(); + /** handle any error thrown by the parser. */ + bool virtual handleError(const COLLADASaxFWL::IError *error); + /** True if there was an error during parsing. */ + bool hasError() + { + return mError; + } + + private: + /** Disable default copy ctor. */ + ErrorHandler(const ErrorHandler &pre); + /** Disable default assignment operator. */ + const ErrorHandler &operator=(const ErrorHandler &pre); + /** Hold error status. */ + bool mError; +}; + +#endif /* __ERRORHANDLER_H__ */ diff --git a/source/blender/io/collada/ExportSettings.cpp b/source/blender/io/collada/ExportSettings.cpp new file mode 100644 index 00000000000..da3c0de0fdf --- /dev/null +++ b/source/blender/io/collada/ExportSettings.cpp @@ -0,0 +1,21 @@ +/* + * 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 collada + */ + +#include "ExportSettings.h" diff --git a/source/blender/io/collada/ExportSettings.h b/source/blender/io/collada/ExportSettings.h new file mode 100644 index 00000000000..1e158418120 --- /dev/null +++ b/source/blender/io/collada/ExportSettings.h @@ -0,0 +1,295 @@ +/* + * 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 collada + */ + +#ifndef __EXPORTSETTINGS_H__ +#define __EXPORTSETTINGS_H__ + +#ifdef __cplusplus +# include <vector> +# include "BCMath.h" + +extern "C" { +#endif + +#include "BLI_linklist.h" +#include "BlenderContext.h" + +typedef enum BC_export_mesh_type { + BC_MESH_TYPE_VIEW, + BC_MESH_TYPE_RENDER, +} BC_export_mesh_type; + +typedef enum BC_export_transformation_type { + BC_TRANSFORMATION_TYPE_MATRIX, + BC_TRANSFORMATION_TYPE_DECOMPOSED, +} BC_export_transformation_type; + +typedef enum BC_export_animation_type { + BC_ANIMATION_EXPORT_SAMPLES, + BC_ANIMATION_EXPORT_KEYS, +} BC_export_animation_type; + +typedef enum BC_ui_export_section { + BC_UI_SECTION_MAIN, + BC_UI_SECTION_GEOMETRY, + BC_UI_SECTION_ARMATURE, + BC_UI_SECTION_ANIMATION, + BC_UI_SECTION_COLLADA, +} BC_ui_export_section; + +typedef struct ExportSettings { + bool apply_modifiers; + BC_global_forward_axis global_forward; + BC_global_up_axis global_up; + bool apply_global_orientation; + + BC_export_mesh_type export_mesh_type; + + bool selected; + bool include_children; + bool include_armatures; + bool include_shapekeys; + bool deform_bones_only; + bool include_animations; + bool include_all_actions; + int sampling_rate; + bool keep_smooth_curves; + bool keep_keyframes; + bool keep_flat_curves; + + bool active_uv_only; + BC_export_animation_type export_animation_type; + bool use_texture_copies; + + bool triangulate; + bool use_object_instantiation; + bool use_blender_profile; + bool sort_by_name; + BC_export_transformation_type object_transformation_type; + BC_export_transformation_type animation_transformation_type; + + bool open_sim; + bool limit_precision; + bool keep_bind_info; + + char *filepath; + LinkNode *export_set; +} ExportSettings; + +#ifdef __cplusplus +} + +void bc_get_children(std::vector<Object *> &child_set, Object *ob, ViewLayer *view_layer); + +class BCExportSettings { + + private: + const ExportSettings &export_settings; + BlenderContext &blender_context; + const BCMatrix global_transform; + + public: + BCExportSettings(ExportSettings *exportSettings, BlenderContext &blenderContext) + : export_settings(*exportSettings), + blender_context(blenderContext), + global_transform(BCMatrix(exportSettings->global_forward, exportSettings->global_up)) + + { + } + + const BCMatrix &get_global_transform() + { + return global_transform; + } + + bool get_apply_modifiers() + { + return export_settings.apply_modifiers; + } + + BC_global_forward_axis get_global_forward() + { + return export_settings.global_forward; + } + + BC_global_up_axis get_global_up() + { + return export_settings.global_up; + } + + bool get_apply_global_orientation() + { + return export_settings.apply_global_orientation; + } + + BC_export_mesh_type get_export_mesh_type() + { + return export_settings.export_mesh_type; + } + + bool get_selected() + { + return export_settings.selected; + } + + bool get_include_children() + { + return export_settings.include_children; + } + + bool get_include_armatures() + { + return export_settings.include_armatures; + } + + bool get_include_shapekeys() + { + return export_settings.include_shapekeys; + } + + bool get_deform_bones_only() + { + return export_settings.deform_bones_only; + } + + bool get_include_animations() + { + return export_settings.include_animations; + } + + bool get_include_all_actions() + { + return export_settings.include_all_actions; + } + + int get_sampling_rate() + { + return export_settings.sampling_rate; + } + + bool get_keep_smooth_curves() + { + return export_settings.keep_smooth_curves; + } + + bool get_keep_keyframes() + { + return export_settings.keep_keyframes; + } + + bool get_keep_flat_curves() + { + return export_settings.keep_flat_curves; + } + + bool get_active_uv_only() + { + return export_settings.active_uv_only; + } + + BC_export_animation_type get_export_animation_type() + { + return export_settings.export_animation_type; + } + + bool get_use_texture_copies() + { + return export_settings.use_texture_copies; + } + + bool get_triangulate() + { + return export_settings.triangulate; + } + + bool get_use_object_instantiation() + { + return export_settings.use_object_instantiation; + } + + bool get_use_blender_profile() + { + return export_settings.use_blender_profile; + } + + bool get_sort_by_name() + { + return export_settings.sort_by_name; + } + + BC_export_transformation_type get_object_transformation_type() + { + return export_settings.object_transformation_type; + } + + BC_export_transformation_type get_animation_transformation_type() + { + return export_settings.animation_transformation_type; + } + + bool get_open_sim() + { + return export_settings.open_sim; + } + + bool get_limit_precision() + { + return export_settings.limit_precision; + } + + bool get_keep_bind_info() + { + return export_settings.keep_bind_info; + } + + char *get_filepath() + { + return export_settings.filepath; + } + + LinkNode *get_export_set() + { + return export_settings.export_set; + } + + BlenderContext &get_blender_context() + { + return blender_context; + } + + Scene *get_scene() + { + return blender_context.get_scene(); + } + + ViewLayer *get_view_layer() + { + return blender_context.get_view_layer(); + } + + bool is_export_root(Object *ob) + { + return bc_is_base_node(get_export_set(), ob, get_view_layer()); + } +}; + +#endif + +#endif diff --git a/source/blender/io/collada/ExtraHandler.cpp b/source/blender/io/collada/ExtraHandler.cpp new file mode 100644 index 00000000000..4875ee72b0f --- /dev/null +++ b/source/blender/io/collada/ExtraHandler.cpp @@ -0,0 +1,93 @@ +/* + * 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 collada + */ + +#include <stddef.h> +#include "BLI_string.h" + +#include "ExtraHandler.h" + +ExtraHandler::ExtraHandler(DocumentImporter *dimp, AnimationImporter *aimp) : currentExtraTags(0) +{ + this->dimp = dimp; + this->aimp = aimp; +} + +ExtraHandler::~ExtraHandler() +{ +} + +bool ExtraHandler::elementBegin(const char *elementName, const char **attributes) +{ + /* \todo attribute handling for profile tags */ + currentElement = std::string(elementName); + // addToSidTree(attributes[0], attributes[1]); + return true; +} + +bool ExtraHandler::elementEnd(const char *elementName) +{ + return true; +} + +bool ExtraHandler::textData(const char *text, size_t textLength) +{ + char buf[1024]; + + if (currentElement.length() == 0 || currentExtraTags == 0) { + return false; + } + + BLI_strncpy(buf, text, textLength + 1); + currentExtraTags->addTag(currentElement, std::string(buf)); + return true; +} + +bool ExtraHandler::parseElement(const char *profileName, + const unsigned long &elementHash, + const COLLADAFW::UniqueId &uniqueId) +{ + /* implement for backwards compatibility, new version added object parameter */ + return parseElement(profileName, elementHash, uniqueId, NULL); +} + +bool ExtraHandler::parseElement(const char *profileName, + const unsigned long &elementHash, + const COLLADAFW::UniqueId &uniqueId, + COLLADAFW::Object *object) +{ + if (BLI_strcaseeq(profileName, "blender")) { +#if 0 + printf("In parseElement for supported profile %s for id %s\n", + profileName, + uniqueId.toAscii().c_str()); +#endif + currentUid = uniqueId; + ExtraTags *et = dimp->getExtraTags(uniqueId); + if (!et) { + et = new ExtraTags(std::string(profileName)); + dimp->addExtraTags(uniqueId, et); + } + currentExtraTags = et; + return true; + } + // printf("In parseElement for unsupported profile %s for id %s\n", profileName, + // uniqueId.toAscii().c_str()); + return false; +} diff --git a/source/blender/io/collada/ExtraHandler.h b/source/blender/io/collada/ExtraHandler.h new file mode 100644 index 00000000000..021eb8e9663 --- /dev/null +++ b/source/blender/io/collada/ExtraHandler.h @@ -0,0 +1,83 @@ +/* + * 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 collada + */ + +#ifndef __EXTRAHANDLER_H__ +#define __EXTRAHANDLER_H__ + +#include <string> +#include <map> +#include <vector> +#include <algorithm> // sort() + +#include "COLLADASaxFWLIExtraDataCallbackHandler.h" +#include "COLLADASaxFWLFilePartLoader.h" +#include "COLLADASWInstanceController.h" + +#include "DocumentImporter.h" +#include "AnimationImporter.h" + +/** \brief Handler class for \<extra\> data, through which different + * profiles can be handled + */ +class ExtraHandler : public COLLADASaxFWL::IExtraDataCallbackHandler { + public: + /** Constructor. */ + ExtraHandler(DocumentImporter *dimp, AnimationImporter *aimp); + + /** Destructor. */ + virtual ~ExtraHandler(); + + /** Handle the beginning of an element. */ + bool elementBegin(const char *elementName, const char **attributes); + + /** Handle the end of an element. */ + bool elementEnd(const char *elementName); + + /** Receive the data in text format. */ + bool textData(const char *text, size_t textLength); + + /** Method to ask, if the current callback handler want to read the data of the given extra + * element. */ + bool parseElement(const char *profileName, + const unsigned long &elementHash, + const COLLADAFW::UniqueId &uniqueId, + COLLADAFW::Object *object); + + /** For backwards compatibility with older OpenCollada, new version added object parameter */ + bool parseElement(const char *profileName, + const unsigned long &elementHash, + const COLLADAFW::UniqueId &uniqueId); + + private: + /** Disable default copy constructor. */ + ExtraHandler(const ExtraHandler &pre); + /** Disable default assignment operator. */ + const ExtraHandler &operator=(const ExtraHandler &pre); + + /** Handle to DocumentImporter for interface to extra element data saving. */ + DocumentImporter *dimp; + AnimationImporter *aimp; + /** Holds Id of element for which <extra> XML elements are handled. */ + COLLADAFW::UniqueId currentUid; + ExtraTags *currentExtraTags; + std::string currentElement; +}; + +#endif /* __EXTRAHANDLER_H__ */ diff --git a/source/blender/io/collada/ExtraTags.cpp b/source/blender/io/collada/ExtraTags.cpp new file mode 100644 index 00000000000..496ba3891f7 --- /dev/null +++ b/source/blender/io/collada/ExtraTags.cpp @@ -0,0 +1,126 @@ +/* + * 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 collada + */ + +#include <stddef.h> +#include <stdlib.h> +#include "BLI_string.h" + +#include <iostream> + +#include "ExtraTags.h" + +ExtraTags::ExtraTags(std::string profile) +{ + this->profile = profile; + this->tags = std::map<std::string, std::string>(); +} + +ExtraTags::~ExtraTags() +{ +} + +bool ExtraTags::isProfile(std::string profile) +{ + return this->profile == profile; +} + +bool ExtraTags::addTag(std::string tag, std::string data) +{ + tags[tag] = data; + + return true; +} + +int ExtraTags::asInt(std::string tag, bool *ok) +{ + if (tags.find(tag) == tags.end()) { + *ok = false; + return -1; + } + *ok = true; + return atoi(tags[tag].c_str()); +} + +float ExtraTags::asFloat(std::string tag, bool *ok) +{ + if (tags.find(tag) == tags.end()) { + *ok = false; + return -1.0f; + } + *ok = true; + return (float)atof(tags[tag].c_str()); +} + +std::string ExtraTags::asString(std::string tag, bool *ok) +{ + if (tags.find(tag) == tags.end()) { + *ok = false; + return ""; + } + *ok = true; + return tags[tag]; +} + +bool ExtraTags::setData(std::string tag, short *data) +{ + bool ok = false; + int tmp = asInt(tag, &ok); + if (ok) { + *data = (short)tmp; + } + return ok; +} + +bool ExtraTags::setData(std::string tag, int *data) +{ + bool ok = false; + int tmp = asInt(tag, &ok); + if (ok) { + *data = tmp; + } + return ok; +} + +bool ExtraTags::setData(std::string tag, float *data) +{ + bool ok = false; + float tmp = asFloat(tag, &ok); + if (ok) { + *data = tmp; + } + return ok; +} + +bool ExtraTags::setData(std::string tag, char *data) +{ + bool ok = false; + int tmp = asInt(tag, &ok); + if (ok) { + *data = (char)tmp; + } + return ok; +} + +std::string ExtraTags::setData(std::string tag, std::string &data) +{ + bool ok = false; + std::string tmp = asString(tag, &ok); + return (ok) ? tmp : data; +} diff --git a/source/blender/io/collada/ExtraTags.h b/source/blender/io/collada/ExtraTags.h new file mode 100644 index 00000000000..9191182c757 --- /dev/null +++ b/source/blender/io/collada/ExtraTags.h @@ -0,0 +1,77 @@ +/* + * 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 collada + */ + +#ifndef __EXTRATAGS_H__ +#define __EXTRATAGS_H__ + +#include <string> +#include <map> +#include <vector> + +/** \brief Class for saving \<extra\> tags for a specific UniqueId. + */ +class ExtraTags { + public: + /** Constructor. */ + ExtraTags(const std::string profile); + + /** Destructor. */ + virtual ~ExtraTags(); + + /** Handle the beginning of an element. */ + bool addTag(std::string tag, std::string data); + + /** Set given short pointer to value of tag, if it exists. */ + bool setData(std::string tag, short *data); + + /** Set given int pointer to value of tag, if it exists. */ + bool setData(std::string tag, int *data); + + /** Set given float pointer to value of tag, if it exists. */ + bool setData(std::string tag, float *data); + + /** Set given char pointer to value of tag, if it exists. */ + bool setData(std::string tag, char *data); + std::string setData(std::string tag, std::string &data); + + /** Return true if the extra tags is for specified profile. */ + bool isProfile(std::string profile); + + private: + /** Disable default copy constructor. */ + ExtraTags(const ExtraTags &pre); + /** Disable default assignment operator. */ + const ExtraTags &operator=(const ExtraTags &pre); + + /** The profile for which the tags are. */ + std::string profile; + + /** Map of tag and text pairs. */ + std::map<std::string, std::string> tags; + + /** Get text data for tag as an int. */ + int asInt(std::string tag, bool *ok); + /** Get text data for tag as a float. */ + float asFloat(std::string tag, bool *ok); + /** Get text data for tag as a string. */ + std::string asString(std::string tag, bool *ok); +}; + +#endif /* __EXTRATAGS_H__ */ diff --git a/source/blender/io/collada/GeometryExporter.cpp b/source/blender/io/collada/GeometryExporter.cpp new file mode 100644 index 00000000000..640bf3c0633 --- /dev/null +++ b/source/blender/io/collada/GeometryExporter.cpp @@ -0,0 +1,718 @@ +/* + * 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 collada + */ + +#include <sstream> + +#include "COLLADASWPrimitves.h" +#include "COLLADASWSource.h" +#include "COLLADASWVertices.h" +#include "COLLADABUUtils.h" + +#include "GeometryExporter.h" + +#include "DNA_meshdata_types.h" + +extern "C" { +#include "BLI_utildefines.h" + +#include "BKE_customdata.h" +#include "BKE_global.h" +#include "BKE_lib_id.h" +#include "BKE_material.h" +#include "BKE_mesh.h" +} + +#include "collada_internal.h" +#include "collada_utils.h" + +void GeometryExporter::exportGeom() +{ + Scene *sce = blender_context.get_scene(); + openLibrary(); + + GeometryFunctor gf; + gf.forEachMeshObjectInExportSet<GeometryExporter>( + sce, *this, this->export_settings.get_export_set()); + + closeLibrary(); +} + +void GeometryExporter::operator()(Object *ob) +{ + bool use_instantiation = this->export_settings.get_use_object_instantiation(); + Mesh *me = bc_get_mesh_copy(blender_context, + ob, + this->export_settings.get_export_mesh_type(), + this->export_settings.get_apply_modifiers(), + this->export_settings.get_triangulate()); + + std::string geom_id = get_geometry_id(ob, use_instantiation); + std::vector<Normal> nor; + std::vector<BCPolygonNormalsIndices> norind; + + /* Skip if linked geometry was already exported from another reference */ + if (use_instantiation && exportedGeometry.find(geom_id) != exportedGeometry.end()) { + return; + } + + std::string geom_name = (use_instantiation) ? id_name(ob->data) : id_name(ob); + geom_name = encode_xml(geom_name); + + exportedGeometry.insert(geom_id); + + bool has_color = (bool)CustomData_has_layer(&me->fdata, CD_MCOL); + + create_normals(nor, norind, me); + + /* openMesh(geoId, geoName, meshId) */ + openMesh(geom_id, geom_name); + + /* writes <source> for vertex coords */ + createVertsSource(geom_id, me); + + /* writes <source> for normal coords */ + createNormalsSource(geom_id, me, nor); + + bool has_uvs = (bool)CustomData_has_layer(&me->ldata, CD_MLOOPUV); + + /* writes <source> for uv coords if mesh has uv coords */ + if (has_uvs) { + createTexcoordsSource(geom_id, me); + } + + if (has_color) { + createVertexColorSource(geom_id, me); + } + /* <vertices> */ + + COLLADASW::Vertices verts(mSW); + verts.setId(getIdBySemantics(geom_id, COLLADASW::InputSemantic::VERTEX)); + COLLADASW::InputList &input_list = verts.getInputList(); + COLLADASW::Input input(COLLADASW::InputSemantic::POSITION, + getUrlBySemantics(geom_id, COLLADASW::InputSemantic::POSITION)); + input_list.push_back(input); + verts.add(); + + createLooseEdgeList(ob, me, geom_id); + + /* Only create Polylists if number of faces > 0 */ + if (me->totface > 0) { + /* XXX slow */ + if (ob->totcol) { + for (int a = 0; a < ob->totcol; a++) { + create_mesh_primitive_list(a, has_uvs, has_color, ob, me, geom_id, norind); + } + } + else { + create_mesh_primitive_list(0, has_uvs, has_color, ob, me, geom_id, norind); + } + } + + closeMesh(); + + closeGeometry(); + + if (this->export_settings.get_include_shapekeys()) { + Key *key = BKE_key_from_object(ob); + if (key) { + KeyBlock *kb = (KeyBlock *)key->block.first; + /* skip the basis */ + kb = kb->next; + for (; kb; kb = kb->next) { + BKE_keyblock_convert_to_mesh(kb, me); + export_key_mesh(ob, me, kb); + } + } + } + + BKE_id_free(NULL, me); +} + +void GeometryExporter::export_key_mesh(Object *ob, Mesh *me, KeyBlock *kb) +{ + std::string geom_id = get_geometry_id(ob, false) + "_morph_" + translate_id(kb->name); + std::vector<Normal> nor; + std::vector<BCPolygonNormalsIndices> norind; + + if (exportedGeometry.find(geom_id) != exportedGeometry.end()) { + return; + } + + std::string geom_name = kb->name; + + exportedGeometry.insert(geom_id); + + bool has_color = (bool)CustomData_has_layer(&me->fdata, CD_MCOL); + + create_normals(nor, norind, me); + + // openMesh(geoId, geoName, meshId) + openMesh(geom_id, geom_name); + + /* writes <source> for vertex coords */ + createVertsSource(geom_id, me); + + /* writes <source> for normal coords */ + createNormalsSource(geom_id, me, nor); + + bool has_uvs = (bool)CustomData_has_layer(&me->ldata, CD_MLOOPUV); + + /* writes <source> for uv coords if mesh has uv coords */ + if (has_uvs) { + createTexcoordsSource(geom_id, me); + } + + if (has_color) { + createVertexColorSource(geom_id, me); + } + + /* <vertices> */ + + COLLADASW::Vertices verts(mSW); + verts.setId(getIdBySemantics(geom_id, COLLADASW::InputSemantic::VERTEX)); + COLLADASW::InputList &input_list = verts.getInputList(); + COLLADASW::Input input(COLLADASW::InputSemantic::POSITION, + getUrlBySemantics(geom_id, COLLADASW::InputSemantic::POSITION)); + input_list.push_back(input); + verts.add(); + + // createLooseEdgeList(ob, me, geom_id, norind); + + /* XXX slow */ + if (ob->totcol) { + for (int a = 0; a < ob->totcol; a++) { + create_mesh_primitive_list(a, has_uvs, has_color, ob, me, geom_id, norind); + } + } + else { + create_mesh_primitive_list(0, has_uvs, has_color, ob, me, geom_id, norind); + } + + closeMesh(); + + closeGeometry(); +} + +void GeometryExporter::createLooseEdgeList(Object *ob, Mesh *me, std::string &geom_id) +{ + + MEdge *medges = me->medge; + int totedges = me->totedge; + int edges_in_linelist = 0; + std::vector<unsigned int> edge_list; + int index; + + /* Find all loose edges in Mesh + * and save vertex indices in edge_list */ + for (index = 0; index < totedges; index++) { + MEdge *edge = &medges[index]; + + if (edge->flag & ME_LOOSEEDGE) { + edges_in_linelist += 1; + edge_list.push_back(edge->v1); + edge_list.push_back(edge->v2); + } + } + + if (edges_in_linelist > 0) { + /* Create the list of loose edges */ + COLLADASW::Lines lines(mSW); + + lines.setCount(edges_in_linelist); + + COLLADASW::InputList &til = lines.getInputList(); + + /* creates <input> in <lines> for vertices */ + COLLADASW::Input input1(COLLADASW::InputSemantic::VERTEX, + getUrlBySemantics(geom_id, COLLADASW::InputSemantic::VERTEX), + 0); + til.push_back(input1); + + lines.prepareToAppendValues(); + + for (index = 0; index < edges_in_linelist; index++) { + lines.appendValues(edge_list[2 * index + 1]); + lines.appendValues(edge_list[2 * index]); + } + lines.finish(); + } +} + +static void prepareToAppendValues(bool is_triangulated, + COLLADASW::PrimitivesBase &primitive_list, + std::vector<unsigned long> &vcount_list) +{ + /* performs the actual writing */ + if (is_triangulated) { + ((COLLADASW::Triangles &)primitive_list).prepareToAppendValues(); + } + else { + /* sets <vcount> */ + primitive_list.setVCountList(vcount_list); + ((COLLADASW::Polylist &)primitive_list).prepareToAppendValues(); + } +} + +static void finish_and_delete_primitive_List(bool is_triangulated, + COLLADASW::PrimitivesBase *primitive_list) +{ + if (is_triangulated) { + ((COLLADASW::Triangles *)primitive_list)->finish(); + } + else { + ((COLLADASW::Polylist *)primitive_list)->finish(); + } + delete primitive_list; +} + +static COLLADASW::PrimitivesBase *create_primitive_list(bool is_triangulated, + COLLADASW::StreamWriter *mSW) +{ + COLLADASW::PrimitivesBase *primitive_list; + + if (is_triangulated) { + primitive_list = new COLLADASW::Triangles(mSW); + } + else { + primitive_list = new COLLADASW::Polylist(mSW); + } + return primitive_list; +} + +static bool collect_vertex_counts_per_poly(Mesh *me, + int material_index, + std::vector<unsigned long> &vcount_list) +{ + MPoly *mpolys = me->mpoly; + int totpolys = me->totpoly; + bool is_triangulated = true; + + int i; + /* Expecting that p->mat_nr is always 0 if the mesh has no materials assigned */ + for (i = 0; i < totpolys; i++) { + MPoly *p = &mpolys[i]; + if (p->mat_nr == material_index) { + int vertex_count = p->totloop; + vcount_list.push_back(vertex_count); + if (vertex_count != 3) { + is_triangulated = false; + } + } + } + return is_triangulated; +} + +std::string GeometryExporter::makeVertexColorSourceId(std::string &geom_id, char *layer_name) +{ + std::string result = getIdBySemantics(geom_id, COLLADASW::InputSemantic::COLOR) + "-" + + layer_name; + return result; +} + +/* powerful because it handles both cases when there is material and when there's not */ +void GeometryExporter::create_mesh_primitive_list(short material_index, + bool has_uvs, + bool has_color, + Object *ob, + Mesh *me, + std::string &geom_id, + std::vector<BCPolygonNormalsIndices> &norind) +{ + + MPoly *mpolys = me->mpoly; + MLoop *mloops = me->mloop; + int totpolys = me->totpoly; + + std::vector<unsigned long> vcount_list; + + bool is_triangulated = collect_vertex_counts_per_poly(me, material_index, vcount_list); + int polygon_count = vcount_list.size(); + + /* no faces using this material */ + if (polygon_count == 0) { + fprintf( + stderr, "%s: material with index %d is not used.\n", id_name(ob).c_str(), material_index); + return; + } + + Material *ma = ob->totcol ? BKE_object_material_get(ob, material_index + 1) : NULL; + COLLADASW::PrimitivesBase *primitive_list = create_primitive_list(is_triangulated, mSW); + + /* sets count attribute in <polylist> */ + primitive_list->setCount(polygon_count); + + /* sets material name */ + if (ma) { + std::string material_id = get_material_id(ma); + std::ostringstream ostr; + ostr << translate_id(material_id); + primitive_list->setMaterial(ostr.str()); + } + + COLLADASW::Input vertex_input(COLLADASW::InputSemantic::VERTEX, + getUrlBySemantics(geom_id, COLLADASW::InputSemantic::VERTEX), + 0); + COLLADASW::Input normals_input(COLLADASW::InputSemantic::NORMAL, + getUrlBySemantics(geom_id, COLLADASW::InputSemantic::NORMAL), + 1); + + COLLADASW::InputList &til = primitive_list->getInputList(); + til.push_back(vertex_input); + til.push_back(normals_input); + + /* if mesh has uv coords writes <input> for TEXCOORD */ + int num_layers = CustomData_number_of_layers(&me->ldata, CD_MLOOPUV); + int active_uv_index = CustomData_get_active_layer_index(&me->ldata, CD_MLOOPUV); + for (int i = 0; i < num_layers; i++) { + int layer_index = CustomData_get_layer_index_n(&me->ldata, CD_MLOOPUV, i); + if (!this->export_settings.get_active_uv_only() || layer_index == active_uv_index) { + + // char *name = CustomData_get_layer_name(&me->ldata, CD_MLOOPUV, i); + COLLADASW::Input texcoord_input( + COLLADASW::InputSemantic::TEXCOORD, + makeUrl(makeTexcoordSourceId(geom_id, i, this->export_settings.get_active_uv_only())), + 2, // this is only until we have optimized UV sets + (this->export_settings.get_active_uv_only()) ? 0 : layer_index - 1 /* set (0,1,2,...) */ + ); + til.push_back(texcoord_input); + } + } + + int totlayer_mcol = CustomData_number_of_layers(&me->ldata, CD_MLOOPCOL); + if (totlayer_mcol > 0) { + int map_index = 0; + + for (int a = 0; a < totlayer_mcol; a++) { + char *layer_name = bc_CustomData_get_layer_name(&me->ldata, CD_MLOOPCOL, a); + COLLADASW::Input input4(COLLADASW::InputSemantic::COLOR, + makeUrl(makeVertexColorSourceId(geom_id, layer_name)), + (has_uvs) ? 3 : 2, // all color layers have same index order + map_index // set number equals color map index + ); + til.push_back(input4); + map_index++; + } + } + + /* performs the actual writing */ + prepareToAppendValues(is_triangulated, *primitive_list, vcount_list); + + /* <p> */ + int texindex = 0; + for (int i = 0; i < totpolys; i++) { + MPoly *p = &mpolys[i]; + int loop_count = p->totloop; + + if (p->mat_nr == material_index) { + MLoop *l = &mloops[p->loopstart]; + BCPolygonNormalsIndices normal_indices = norind[i]; + + for (int j = 0; j < loop_count; j++) { + primitive_list->appendValues(l[j].v); + primitive_list->appendValues(normal_indices[j]); + if (has_uvs) { + primitive_list->appendValues(texindex + j); + } + + if (has_color) { + primitive_list->appendValues(texindex + j); + } + } + } + + texindex += loop_count; + } + + finish_and_delete_primitive_List(is_triangulated, primitive_list); +} + +/* creates <source> for positions */ +void GeometryExporter::createVertsSource(std::string geom_id, Mesh *me) +{ +#if 0 + int totverts = dm->getNumVerts(dm); + MVert *verts = dm->getVertArray(dm); +#endif + int totverts = me->totvert; + MVert *verts = me->mvert; + + COLLADASW::FloatSourceF source(mSW); + source.setId(getIdBySemantics(geom_id, COLLADASW::InputSemantic::POSITION)); + source.setArrayId(getIdBySemantics(geom_id, COLLADASW::InputSemantic::POSITION) + + ARRAY_ID_SUFFIX); + source.setAccessorCount(totverts); + source.setAccessorStride(3); + + COLLADASW::SourceBase::ParameterNameList ¶m = source.getParameterNameList(); + param.push_back("X"); + param.push_back("Y"); + param.push_back("Z"); + /* main function, it creates <source id = "">, <float_array id = "" + * count = ""> */ + source.prepareToAppendValues(); + /* appends data to <float_array> */ + int i = 0; + for (i = 0; i < totverts; i++) { + Vector co; + if (export_settings.get_apply_global_orientation()) { + bc_add_global_transform(co, verts[i].co, export_settings.get_global_transform()); + } + else { + copy_v3_v3(co, verts[i].co); + } + source.appendValues(co[0], co[1], co[2]); + } + + source.finish(); +} + +void GeometryExporter::createVertexColorSource(std::string geom_id, Mesh *me) +{ + /* Find number of vertex color layers */ + int totlayer_mcol = CustomData_number_of_layers(&me->ldata, CD_MLOOPCOL); + if (totlayer_mcol == 0) { + return; + } + + int map_index = 0; + for (int a = 0; a < totlayer_mcol; a++) { + + map_index++; + MLoopCol *mloopcol = (MLoopCol *)CustomData_get_layer_n(&me->ldata, CD_MLOOPCOL, a); + + COLLADASW::FloatSourceF source(mSW); + + char *layer_name = bc_CustomData_get_layer_name(&me->ldata, CD_MLOOPCOL, a); + std::string layer_id = makeVertexColorSourceId(geom_id, layer_name); + source.setId(layer_id); + + source.setNodeName(layer_name); + + source.setArrayId(layer_id + ARRAY_ID_SUFFIX); + source.setAccessorCount(me->totloop); + source.setAccessorStride(4); + + COLLADASW::SourceBase::ParameterNameList ¶m = source.getParameterNameList(); + param.push_back("R"); + param.push_back("G"); + param.push_back("B"); + param.push_back("A"); + + source.prepareToAppendValues(); + + MPoly *mpoly; + int i; + for (i = 0, mpoly = me->mpoly; i < me->totpoly; i++, mpoly++) { + MLoopCol *mlc = mloopcol + mpoly->loopstart; + for (int j = 0; j < mpoly->totloop; j++, mlc++) { + source.appendValues(mlc->r / 255.0f, mlc->g / 255.0f, mlc->b / 255.0f, mlc->a / 255.0f); + } + } + + source.finish(); + } +} + +std::string GeometryExporter::makeTexcoordSourceId(std::string &geom_id, + int layer_index, + bool is_single_layer) +{ + char suffix[20]; + if (is_single_layer) { + suffix[0] = '\0'; + } + else { + sprintf(suffix, "-%d", layer_index); + } + return getIdBySemantics(geom_id, COLLADASW::InputSemantic::TEXCOORD) + suffix; +} + +/* creates <source> for texcoords */ +void GeometryExporter::createTexcoordsSource(std::string geom_id, Mesh *me) +{ + + int totpoly = me->totpoly; + int totuv = me->totloop; + MPoly *mpolys = me->mpoly; + + int num_layers = CustomData_number_of_layers(&me->ldata, CD_MLOOPUV); + + /* write <source> for each layer + * each <source> will get id like meshName + "map-channel-1" */ + int active_uv_index = CustomData_get_active_layer_index(&me->ldata, CD_MLOOPUV); + for (int a = 0; a < num_layers; a++) { + int layer_index = CustomData_get_layer_index_n(&me->ldata, CD_MLOOPUV, a); + if (!this->export_settings.get_active_uv_only() || layer_index == active_uv_index) { + MLoopUV *mloops = (MLoopUV *)CustomData_get_layer_n(&me->ldata, CD_MLOOPUV, a); + + COLLADASW::FloatSourceF source(mSW); + std::string layer_id = makeTexcoordSourceId( + geom_id, a, this->export_settings.get_active_uv_only()); + source.setId(layer_id); + source.setArrayId(layer_id + ARRAY_ID_SUFFIX); + + source.setAccessorCount(totuv); + source.setAccessorStride(2); + COLLADASW::SourceBase::ParameterNameList ¶m = source.getParameterNameList(); + param.push_back("S"); + param.push_back("T"); + + source.prepareToAppendValues(); + + for (int index = 0; index < totpoly; index++) { + MPoly *mpoly = mpolys + index; + MLoopUV *mloop = mloops + mpoly->loopstart; + for (int j = 0; j < mpoly->totloop; j++) { + source.appendValues(mloop[j].uv[0], mloop[j].uv[1]); + } + } + + source.finish(); + } + } +} + +bool operator<(const Normal &a, const Normal &b) +{ + /* only needed to sort normal vectors and find() them later in a map.*/ + return a.x < b.x || (a.x == b.x && (a.y < b.y || (a.y == b.y && a.z < b.z))); +} + +/* creates <source> for normals */ +void GeometryExporter::createNormalsSource(std::string geom_id, Mesh *me, std::vector<Normal> &nor) +{ +#if 0 + int totverts = dm->getNumVerts(dm); + MVert *verts = dm->getVertArray(dm); +#endif + + COLLADASW::FloatSourceF source(mSW); + source.setId(getIdBySemantics(geom_id, COLLADASW::InputSemantic::NORMAL)); + source.setArrayId(getIdBySemantics(geom_id, COLLADASW::InputSemantic::NORMAL) + ARRAY_ID_SUFFIX); + source.setAccessorCount((unsigned long)nor.size()); + source.setAccessorStride(3); + COLLADASW::SourceBase::ParameterNameList ¶m = source.getParameterNameList(); + param.push_back("X"); + param.push_back("Y"); + param.push_back("Z"); + + source.prepareToAppendValues(); + + std::vector<Normal>::iterator it; + for (it = nor.begin(); it != nor.end(); it++) { + Normal &n = *it; + + Vector no{n.x, n.y, n.z}; + if (export_settings.get_apply_global_orientation()) { + bc_add_global_transform(no, export_settings.get_global_transform()); + } + source.appendValues(no[0], no[1], no[2]); + } + + source.finish(); +} + +void GeometryExporter::create_normals(std::vector<Normal> &normals, + std::vector<BCPolygonNormalsIndices> &polygons_normals, + Mesh *me) +{ + std::map<Normal, unsigned int> shared_normal_indices; + int last_normal_index = -1; + + MVert *verts = me->mvert; + MLoop *mloops = me->mloop; + float(*lnors)[3] = NULL; + bool use_custom_normals = false; + + BKE_mesh_calc_normals_split(me); + if (CustomData_has_layer(&me->ldata, CD_NORMAL)) { + lnors = (float(*)[3])CustomData_get_layer(&me->ldata, CD_NORMAL); + use_custom_normals = true; + } + + for (int poly_index = 0; poly_index < me->totpoly; poly_index++) { + MPoly *mpoly = &me->mpoly[poly_index]; + bool use_vertex_normals = use_custom_normals || mpoly->flag & ME_SMOOTH; + + if (!use_vertex_normals) { + /* For flat faces use face normal as vertex normal: */ + + float vector[3]; + BKE_mesh_calc_poly_normal(mpoly, mloops + mpoly->loopstart, verts, vector); + + Normal n = {vector[0], vector[1], vector[2]}; + normals.push_back(n); + last_normal_index++; + } + + BCPolygonNormalsIndices poly_indices; + for (int loop_index = 0; loop_index < mpoly->totloop; loop_index++) { + unsigned int loop_idx = mpoly->loopstart + loop_index; + if (use_vertex_normals) { + float normalized[3]; + + if (use_custom_normals) { + normalize_v3_v3(normalized, lnors[loop_idx]); + } + else { + normal_short_to_float_v3(normalized, verts[mloops[loop_index].v].no); + normalize_v3(normalized); + } + Normal n = {normalized[0], normalized[1], normalized[2]}; + + if (shared_normal_indices.find(n) != shared_normal_indices.end()) { + poly_indices.add_index(shared_normal_indices[n]); + } + else { + last_normal_index++; + poly_indices.add_index(last_normal_index); + shared_normal_indices[n] = last_normal_index; + normals.push_back(n); + } + } + else { + poly_indices.add_index(last_normal_index); + } + } + + polygons_normals.push_back(poly_indices); + } +} + +std::string GeometryExporter::getIdBySemantics(std::string geom_id, + COLLADASW::InputSemantic::Semantics type, + std::string other_suffix) +{ + return geom_id + getSuffixBySemantic(type) + other_suffix; +} + +COLLADASW::URI GeometryExporter::getUrlBySemantics(std::string geom_id, + COLLADASW::InputSemantic::Semantics type, + std::string other_suffix) +{ + + std::string id(getIdBySemantics(geom_id, type, other_suffix)); + return COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, id); +} + +COLLADASW::URI GeometryExporter::makeUrl(std::string id) +{ + return COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, id); +} diff --git a/source/blender/io/collada/GeometryExporter.h b/source/blender/io/collada/GeometryExporter.h new file mode 100644 index 00000000000..8c7a38fc407 --- /dev/null +++ b/source/blender/io/collada/GeometryExporter.h @@ -0,0 +1,140 @@ +/* + * 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 collada + */ + +#ifndef __GEOMETRYEXPORTER_H__ +#define __GEOMETRYEXPORTER_H__ + +#include <string> +#include <vector> +#include <set> + +#include "COLLADASWStreamWriter.h" +#include "COLLADASWLibraryGeometries.h" +#include "COLLADASWInputList.h" + +#include "DNA_mesh_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_key_types.h" + +#include "ExportSettings.h" +#include "collada_utils.h" +#include "BlenderContext.h" +#include "BKE_key.h" + +class Normal { + public: + float x; + float y; + float z; + + friend bool operator<(const Normal &, const Normal &); +}; + +bool operator<(const Normal &, const Normal &); + +/* TODO: optimize UV sets by making indexed list with duplicates removed */ +class GeometryExporter : COLLADASW::LibraryGeometries { + struct Face { + unsigned int v1, v2, v3, v4; + }; + + public: + /* TODO: optimize UV sets by making indexed list with duplicates removed */ + GeometryExporter(BlenderContext &blender_context, + COLLADASW::StreamWriter *sw, + BCExportSettings &export_settings) + : COLLADASW::LibraryGeometries(sw), + blender_context(blender_context), + export_settings(export_settings) + { + } + + void exportGeom(); + + void operator()(Object *ob); + + void createLooseEdgeList(Object *ob, Mesh *me, std::string &geom_id); + + /* powerful because it handles both cases when there is material and when there's not */ + void create_mesh_primitive_list(short material_index, + bool has_uvs, + bool has_color, + Object *ob, + Mesh *me, + std::string &geom_id, + std::vector<BCPolygonNormalsIndices> &norind); + + /* creates <source> for positions */ + void createVertsSource(std::string geom_id, Mesh *me); + + void createVertexColorSource(std::string geom_id, Mesh *me); + + std::string makeTexcoordSourceId(std::string &geom_id, int layer_index, bool is_single_layer); + + /* creates <source> for texcoords */ + void createTexcoordsSource(std::string geom_id, Mesh *me); + void createTesselatedTexcoordsSource(std::string geom_id, Mesh *me); + + /* creates <source> for normals */ + void createNormalsSource(std::string geom_id, Mesh *me, std::vector<Normal> &nor); + + void create_normals(std::vector<Normal> &nor, + std::vector<BCPolygonNormalsIndices> &ind, + Mesh *me); + + std::string getIdBySemantics(std::string geom_id, + COLLADASW::InputSemantic::Semantics type, + std::string other_suffix = ""); + std::string makeVertexColorSourceId(std::string &geom_id, char *layer_name); + + COLLADASW::URI getUrlBySemantics(std::string geom_id, + COLLADASW::InputSemantic::Semantics type, + std::string other_suffix = ""); + + COLLADASW::URI makeUrl(std::string id); + + void export_key_mesh(Object *ob, Mesh *me, KeyBlock *kb); + + private: + std::set<std::string> exportedGeometry; + BlenderContext &blender_context; + BCExportSettings &export_settings; + + Mesh *get_mesh(Scene *sce, Object *ob, int apply_modifiers); +}; + +struct GeometryFunctor { + /* f should have + * void operator()(Object *ob) */ + template<class Functor> + void forEachMeshObjectInExportSet(Scene *sce, Functor &f, LinkNode *export_set) + { + LinkNode *node; + for (node = export_set; node; node = node->next) { + Object *ob = (Object *)node->link; + if (ob->type == OB_MESH) { + f(ob); + } + } + } +}; + +#endif diff --git a/source/blender/io/collada/ImageExporter.cpp b/source/blender/io/collada/ImageExporter.cpp new file mode 100644 index 00000000000..6e31e17fb26 --- /dev/null +++ b/source/blender/io/collada/ImageExporter.cpp @@ -0,0 +1,169 @@ +/* + * 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 collada + */ + +#include "COLLADABUURI.h" +#include "COLLADASWImage.h" + +extern "C" { +#include "DNA_texture_types.h" +#include "DNA_image_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_customdata.h" +#include "BKE_global.h" +#include "BKE_image.h" +#include "BKE_main.h" +#include "BKE_mesh.h" + +#include "BLI_fileops.h" +#include "BLI_path_util.h" +#include "BLI_string.h" + +#include "IMB_imbuf_types.h" +} + +#include "ImageExporter.h" +#include "MaterialExporter.h" + +ImagesExporter::ImagesExporter(COLLADASW::StreamWriter *sw, + BCExportSettings &export_settings, + KeyImageMap &key_image_map) + : COLLADASW::LibraryImages(sw), export_settings(export_settings), key_image_map(key_image_map) +{ + /* pass */ +} + +void ImagesExporter::export_UV_Image(Image *image, bool use_copies) +{ + std::string name(id_name(image)); + std::string translated_name(translate_id(name)); + + ImBuf *imbuf = BKE_image_acquire_ibuf(image, NULL, NULL); + if (!imbuf) { + fprintf(stderr, "Collada export: image does not exist:\n%s\n", image->name); + return; + } + + bool is_dirty = BKE_image_is_dirty(image); + + ImageFormatData imageFormat; + BKE_imbuf_to_image_format(&imageFormat, imbuf); + + short image_source = image->source; + bool is_generated = image_source == IMA_SRC_GENERATED; + bool is_packed = BKE_image_has_packedfile(image); + + char export_path[FILE_MAX]; + char source_path[FILE_MAX]; + char export_dir[FILE_MAX]; + char export_file[FILE_MAX]; + + /* Destination folder for exported assets */ + BLI_split_dir_part(this->export_settings.get_filepath(), export_dir, sizeof(export_dir)); + + if (is_generated || is_dirty || use_copies || is_packed) { + + /* make absolute destination path */ + + BLI_strncpy(export_file, name.c_str(), sizeof(export_file)); + BKE_image_path_ensure_ext_from_imformat(export_file, &imageFormat); + + BLI_join_dirfile(export_path, sizeof(export_path), export_dir, export_file); + + /* make dest directory if it doesn't exist */ + BLI_make_existing_file(export_path); + } + + if (is_generated || is_dirty || is_packed) { + + /* This image in its current state only exists in Blender memory. + * So we have to export it. The export will keep the image state intact, + * so the exported file will not be associated with the image. */ + + if (BKE_imbuf_write_as(imbuf, export_path, &imageFormat, true) == 0) { + fprintf(stderr, "Collada export: Cannot export image to:\n%s\n", export_path); + return; + } + BLI_strncpy(export_path, export_file, sizeof(export_path)); + } + else { + + /* make absolute source path */ + BLI_strncpy(source_path, image->name, sizeof(source_path)); + BLI_path_abs(source_path, ID_BLEND_PATH_FROM_GLOBAL(&image->id)); + BLI_cleanup_path(NULL, source_path); + + if (use_copies) { + + /* This image is already located on the file system. + * But we want to create copies here. + * To move images into the same export directory. + * Note: If an image is already located in the export folder, + * then skip the copy (as it would result in a file copy error). */ + + if (BLI_path_cmp(source_path, export_path) != 0) { + if (BLI_copy(source_path, export_path) != 0) { + fprintf(stderr, + "Collada export: Cannot copy image:\n source:%s\ndest :%s\n", + source_path, + export_path); + return; + } + } + + BLI_strncpy(export_path, export_file, sizeof(export_path)); + } + else { + + /* Do not make any copies, but use the source path directly as reference + * to the original image */ + + BLI_strncpy(export_path, source_path, sizeof(export_path)); + } + } + + /* Set name also to mNameNC. + * This helps other viewers import files exported from Blender better. */ + COLLADASW::Image img(COLLADABU::URI(COLLADABU::URI::nativePathToUri(export_path)), + translated_name, + translated_name); + img.add(mSW); + fprintf(stdout, "Collada export: Added image: %s\n", export_file); + + BKE_image_release_ibuf(image, imbuf, NULL); +} + +void ImagesExporter::exportImages(Scene *sce) +{ + bool use_texture_copies = this->export_settings.get_use_texture_copies(); + openLibrary(); + + KeyImageMap::iterator iter; + for (iter = key_image_map.begin(); iter != key_image_map.end(); iter++) { + + Image *image = iter->second; + std::string uid(id_name(image)); + std::string key = translate_id(uid); + + export_UV_Image(image, use_texture_copies); + } + + closeLibrary(); +} diff --git a/source/blender/io/collada/ImageExporter.h b/source/blender/io/collada/ImageExporter.h new file mode 100644 index 00000000000..b72d2709382 --- /dev/null +++ b/source/blender/io/collada/ImageExporter.h @@ -0,0 +1,51 @@ +/* + * 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 collada + */ + +#ifndef __IMAGEEXPORTER_H__ +#define __IMAGEEXPORTER_H__ + +#include <vector> +#include <string> + +#include "COLLADASWStreamWriter.h" +#include "COLLADASWLibraryImages.h" + +#include "DNA_material_types.h" +#include "DNA_image_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "ExportSettings.h" +#include "collada_utils.h" + +class ImagesExporter : COLLADASW::LibraryImages { + public: + ImagesExporter(COLLADASW::StreamWriter *sw, + BCExportSettings &export_settings, + KeyImageMap &key_image_map); + void exportImages(Scene *sce); + + private: + BCExportSettings &export_settings; + KeyImageMap &key_image_map; + void export_UV_Image(Image *image, bool use_texture_copies); +}; + +#endif diff --git a/source/blender/io/collada/ImportSettings.cpp b/source/blender/io/collada/ImportSettings.cpp new file mode 100644 index 00000000000..049ee1d0975 --- /dev/null +++ b/source/blender/io/collada/ImportSettings.cpp @@ -0,0 +1,21 @@ +/* + * 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 collada + */ + +#include "ImportSettings.h" diff --git a/source/blender/io/collada/ImportSettings.h b/source/blender/io/collada/ImportSettings.h new file mode 100644 index 00000000000..608d8bff882 --- /dev/null +++ b/source/blender/io/collada/ImportSettings.h @@ -0,0 +1,34 @@ +/* + * 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 collada + */ + +#ifndef __IMPORTSETTINGS_H__ +#define __IMPORTSETTINGS_H__ + +typedef struct ImportSettings { + bool import_units; + bool find_chains; + bool auto_connect; + bool fix_orientation; + int min_chain_length; + char *filepath; + bool keep_bind_info; +} ImportSettings; + +#endif diff --git a/source/blender/io/collada/InstanceWriter.cpp b/source/blender/io/collada/InstanceWriter.cpp new file mode 100644 index 00000000000..c9390d23fe7 --- /dev/null +++ b/source/blender/io/collada/InstanceWriter.cpp @@ -0,0 +1,70 @@ +/* + * 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 collada + */ + +#include <string> +#include <sstream> + +#include "COLLADASWInstanceMaterial.h" + +extern "C" { +#include "BKE_customdata.h" +#include "BKE_material.h" +#include "DNA_mesh_types.h" +} + +#include "InstanceWriter.h" +#include "collada_internal.h" +#include "collada_utils.h" + +void InstanceWriter::add_material_bindings(COLLADASW::BindMaterial &bind_material, + Object *ob, + bool active_uv_only) +{ + for (int a = 0; a < ob->totcol; a++) { + Material *ma = BKE_object_material_get(ob, a + 1); + + COLLADASW::InstanceMaterialList &iml = bind_material.getInstanceMaterialList(); + + if (ma) { + std::string matid(get_material_id(ma)); + matid = translate_id(matid); + std::ostringstream ostr; + ostr << matid; + COLLADASW::InstanceMaterial im(ostr.str(), + COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, matid)); + + // create <bind_vertex_input> for each uv map + Mesh *me = (Mesh *)ob->data; + + int num_layers = CustomData_number_of_layers(&me->ldata, CD_MLOOPUV); + + int map_index = 0; + int active_uv_index = CustomData_get_active_layer_index(&me->ldata, CD_MLOOPUV); + for (int b = 0; b < num_layers; b++) { + if (!active_uv_only || b == active_uv_index) { + char *name = bc_CustomData_get_layer_name(&me->ldata, CD_MLOOPUV, b); + im.push_back(COLLADASW::BindVertexInput(name, "TEXCOORD", map_index++)); + } + } + + iml.push_back(im); + } + } +} diff --git a/source/blender/io/collada/InstanceWriter.h b/source/blender/io/collada/InstanceWriter.h new file mode 100644 index 00000000000..cfec1cf7006 --- /dev/null +++ b/source/blender/io/collada/InstanceWriter.h @@ -0,0 +1,35 @@ +/* + * 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 collada + */ + +#ifndef __INSTANCEWRITER_H__ +#define __INSTANCEWRITER_H__ + +#include "COLLADASWBindMaterial.h" + +#include "DNA_object_types.h" + +class InstanceWriter { + protected: + void add_material_bindings(COLLADASW::BindMaterial &bind_material, + Object *ob, + bool active_uv_only); +}; + +#endif diff --git a/source/blender/io/collada/LightExporter.cpp b/source/blender/io/collada/LightExporter.cpp new file mode 100644 index 00000000000..463981ceefa --- /dev/null +++ b/source/blender/io/collada/LightExporter.cpp @@ -0,0 +1,156 @@ +/* + * 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 collada + */ + +#include <string> + +#include "COLLADASWColor.h" +#include "COLLADASWLight.h" + +#include "BLI_math.h" + +#include "LightExporter.h" +#include "collada_internal.h" + +template<class Functor> +void forEachLightObjectInExportSet(Scene *sce, Functor &f, LinkNode *export_set) +{ + LinkNode *node; + for (node = export_set; node; node = node->next) { + Object *ob = (Object *)node->link; + + if (ob->type == OB_LAMP && ob->data) { + f(ob); + } + } +} + +LightsExporter::LightsExporter(COLLADASW::StreamWriter *sw, BCExportSettings &export_settings) + : COLLADASW::LibraryLights(sw), export_settings(export_settings) +{ +} + +void LightsExporter::exportLights(Scene *sce) +{ + openLibrary(); + + forEachLightObjectInExportSet(sce, *this, this->export_settings.get_export_set()); + + closeLibrary(); +} + +void LightsExporter::operator()(Object *ob) +{ + Light *la = (Light *)ob->data; + std::string la_id(get_light_id(ob)); + std::string la_name(id_name(la)); + COLLADASW::Color col(la->r * la->energy, la->g * la->energy, la->b * la->energy); + float d, constatt, linatt, quadatt; + + d = la->dist; + + constatt = 1.0f; + + if (la->falloff_type == LA_FALLOFF_INVLINEAR) { + linatt = 1.0f / d; + quadatt = 0.0f; + } + else { + linatt = 0.0f; + quadatt = 1.0f / (d * d); + } + + // sun + if (la->type == LA_SUN) { + COLLADASW::DirectionalLight cla(mSW, la_id, la_name); + cla.setColor(col, false, "color"); + cla.setConstantAttenuation(constatt); + exportBlenderProfile(cla, la); + addLight(cla); + } + + // spot + else if (la->type == LA_SPOT) { + COLLADASW::SpotLight cla(mSW, la_id, la_name); + cla.setColor(col, false, "color"); + cla.setFallOffAngle(RAD2DEGF(la->spotsize), false, "fall_off_angle"); + cla.setFallOffExponent(la->spotblend, false, "fall_off_exponent"); + cla.setConstantAttenuation(constatt); + cla.setLinearAttenuation(linatt); + cla.setQuadraticAttenuation(quadatt); + exportBlenderProfile(cla, la); + addLight(cla); + } + // lamp + else if (la->type == LA_LOCAL) { + COLLADASW::PointLight cla(mSW, la_id, la_name); + cla.setColor(col, false, "color"); + cla.setConstantAttenuation(constatt); + cla.setLinearAttenuation(linatt); + cla.setQuadraticAttenuation(quadatt); + exportBlenderProfile(cla, la); + addLight(cla); + } + // area light is not supported + // it will be exported as a local lamp + else { + COLLADASW::PointLight cla(mSW, la_id, la_name); + cla.setColor(col, false, "color"); + cla.setConstantAttenuation(constatt); + cla.setLinearAttenuation(linatt); + cla.setQuadraticAttenuation(quadatt); + exportBlenderProfile(cla, la); + addLight(cla); + } +} + +bool LightsExporter::exportBlenderProfile(COLLADASW::Light &cla, Light *la) +{ + cla.addExtraTechniqueParameter("blender", "type", la->type); + cla.addExtraTechniqueParameter("blender", "flag", la->flag); + cla.addExtraTechniqueParameter("blender", "mode", la->mode); + cla.addExtraTechniqueParameter("blender", "gamma", la->k, "blender_gamma"); + cla.addExtraTechniqueParameter("blender", "red", la->r); + cla.addExtraTechniqueParameter("blender", "green", la->g); + cla.addExtraTechniqueParameter("blender", "blue", la->b); + cla.addExtraTechniqueParameter("blender", "shadow_r", la->shdwr, "blender_shadow_r"); + cla.addExtraTechniqueParameter("blender", "shadow_g", la->shdwg, "blender_shadow_g"); + cla.addExtraTechniqueParameter("blender", "shadow_b", la->shdwb, "blender_shadow_b"); + cla.addExtraTechniqueParameter("blender", "energy", la->energy, "blender_energy"); + cla.addExtraTechniqueParameter("blender", "dist", la->dist, "blender_dist"); + cla.addExtraTechniqueParameter("blender", "spotsize", RAD2DEGF(la->spotsize)); + cla.addExtraTechniqueParameter("blender", "spotblend", la->spotblend); + cla.addExtraTechniqueParameter("blender", "att1", la->att1); + cla.addExtraTechniqueParameter("blender", "att2", la->att2); + // \todo figure out how we can have falloff curve supported here + cla.addExtraTechniqueParameter("blender", "falloff_type", la->falloff_type); + cla.addExtraTechniqueParameter("blender", "clipsta", la->clipsta); + cla.addExtraTechniqueParameter("blender", "clipend", la->clipend); + cla.addExtraTechniqueParameter("blender", "bias", la->bias); + cla.addExtraTechniqueParameter("blender", "soft", la->soft); + cla.addExtraTechniqueParameter("blender", "bufsize", la->bufsize); + cla.addExtraTechniqueParameter("blender", "samp", la->samp); + cla.addExtraTechniqueParameter("blender", "buffers", la->buffers); + cla.addExtraTechniqueParameter("blender", "area_shape", la->area_shape); + cla.addExtraTechniqueParameter("blender", "area_size", la->area_size); + cla.addExtraTechniqueParameter("blender", "area_sizey", la->area_sizey); + cla.addExtraTechniqueParameter("blender", "area_sizez", la->area_sizez); + + return true; +} diff --git a/source/blender/io/collada/LightExporter.h b/source/blender/io/collada/LightExporter.h new file mode 100644 index 00000000000..045ccfe1ce8 --- /dev/null +++ b/source/blender/io/collada/LightExporter.h @@ -0,0 +1,44 @@ +/* + * 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 collada + */ + +#ifndef __LIGHTEXPORTER_H__ +#define __LIGHTEXPORTER_H__ + +#include "COLLADASWStreamWriter.h" +#include "COLLADASWLibraryLights.h" + +#include "DNA_light_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "ExportSettings.h" + +class LightsExporter : COLLADASW::LibraryLights { + public: + LightsExporter(COLLADASW::StreamWriter *sw, BCExportSettings &export_settings); + void exportLights(Scene *sce); + void operator()(Object *ob); + + private: + bool exportBlenderProfile(COLLADASW::Light &cla, Light *la); + BCExportSettings &export_settings; +}; + +#endif diff --git a/source/blender/io/collada/MaterialExporter.cpp b/source/blender/io/collada/MaterialExporter.cpp new file mode 100644 index 00000000000..488d1833e48 --- /dev/null +++ b/source/blender/io/collada/MaterialExporter.cpp @@ -0,0 +1,75 @@ +/* + * 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 collada + */ + +#include "MaterialExporter.h" +#include "COLLADABUUtils.h" +#include "collada_internal.h" + +MaterialsExporter::MaterialsExporter(COLLADASW::StreamWriter *sw, + BCExportSettings &export_settings) + : COLLADASW::LibraryMaterials(sw), export_settings(export_settings) +{ + /* pass */ +} + +void MaterialsExporter::exportMaterials(Scene *sce) +{ + if (hasMaterials(sce)) { + openLibrary(); + + MaterialFunctor mf; + mf.forEachMaterialInExportSet<MaterialsExporter>( + sce, *this, this->export_settings.get_export_set()); + + closeLibrary(); + } +} + +bool MaterialsExporter::hasMaterials(Scene *sce) +{ + LinkNode *node; + for (node = this->export_settings.get_export_set(); node; node = node->next) { + Object *ob = (Object *)node->link; + int a; + for (a = 0; a < ob->totcol; a++) { + Material *ma = BKE_object_material_get(ob, a + 1); + + // no material, but check all of the slots + if (!ma) { + continue; + } + + return true; + } + } + return false; +} + +void MaterialsExporter::operator()(Material *ma, Object *ob) +{ + std::string mat_name = encode_xml(id_name(ma)); + std::string mat_id = get_material_id(ma); + std::string eff_id = get_effect_id(ma); + + openMaterial(mat_id, mat_name); + addInstanceEffect(COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, eff_id)); + + closeMaterial(); +} diff --git a/source/blender/io/collada/MaterialExporter.h b/source/blender/io/collada/MaterialExporter.h new file mode 100644 index 00000000000..be0d939b68a --- /dev/null +++ b/source/blender/io/collada/MaterialExporter.h @@ -0,0 +1,98 @@ +/* + * 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 collada + */ + +#ifndef __MATERIALEXPORTER_H__ +#define __MATERIALEXPORTER_H__ + +#include <string> +#include <vector> + +#include "COLLADASWLibraryMaterials.h" +#include "COLLADASWStreamWriter.h" + +extern "C" { +#include "BKE_material.h" +#include "DNA_material_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +} + +#include "GeometryExporter.h" +#include "collada_internal.h" +#include "ExportSettings.h" +#include "Materials.h" + +class MaterialsExporter : COLLADASW::LibraryMaterials { + public: + MaterialsExporter(COLLADASW::StreamWriter *sw, BCExportSettings &export_settings); + void exportMaterials(Scene *sce); + void operator()(Material *ma, Object *ob); + + private: + bool hasMaterials(Scene *sce); + BCExportSettings &export_settings; +}; + +// used in forEachMaterialInScene +template<class Functor> class ForEachMaterialFunctor { + std::vector<std::string> + mMat; // contains list of material names, to avoid duplicate calling of f + Functor *f; + + public: + ForEachMaterialFunctor(Functor *f) : f(f) + { + } + + void operator()(Object *ob) + { + int a; + for (a = 0; a < ob->totcol; a++) { + + Material *ma = BKE_object_material_get(ob, a + 1); + + if (!ma) { + continue; + } + + std::string translated_id = translate_id(id_name(ma)); + if (find(mMat.begin(), mMat.end(), translated_id) == mMat.end()) { + (*this->f)(ma, ob); + + mMat.push_back(translated_id); + } + } + } +}; + +struct MaterialFunctor { + // calls f for each unique material linked to each object in sce + // f should have + // void operator()(Material *ma) + template<class Functor> + void forEachMaterialInExportSet(Scene *sce, Functor &f, LinkNode *export_set) + { + ForEachMaterialFunctor<Functor> matfunc(&f); + GeometryFunctor gf; + gf.forEachMeshObjectInExportSet<ForEachMaterialFunctor<Functor>>(sce, matfunc, export_set); + } +}; + +#endif diff --git a/source/blender/io/collada/Materials.cpp b/source/blender/io/collada/Materials.cpp new file mode 100644 index 00000000000..06f54884668 --- /dev/null +++ b/source/blender/io/collada/Materials.cpp @@ -0,0 +1,396 @@ +/* + * 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. + */ + +#include "Materials.h" + +MaterialNode::MaterialNode(bContext *C, Material *ma, KeyImageMap &key_image_map) + : mContext(C), material(ma), effect(nullptr), key_image_map(&key_image_map) +{ + bNodeTree *new_ntree = prepare_material_nodetree(); + setShaderType(); + if (new_ntree) { + shader_node = add_node(SH_NODE_BSDF_PRINCIPLED, 0, 300, ""); + output_node = add_node(SH_NODE_OUTPUT_MATERIAL, 300, 300, ""); + add_link(shader_node, 0, output_node, 0); + } +} + +MaterialNode::MaterialNode(bContext *C, + COLLADAFW::EffectCommon *ef, + Material *ma, + UidImageMap &uid_image_map) + : mContext(C), material(ma), effect(ef), uid_image_map(&uid_image_map) +{ + prepare_material_nodetree(); + setShaderType(); + + std::map<std::string, bNode *> nmap; +#if 0 + nmap["main"] = add_node(C, ntree, SH_NODE_BSDF_PRINCIPLED, -300, 300); + nmap["emission"] = add_node(C, ntree, SH_NODE_EMISSION, -300, 500, "emission"); + nmap["add"] = add_node(C, ntree, SH_NODE_ADD_SHADER, 100, 400); + nmap["transparent"] = add_node(C, ntree, SH_NODE_BSDF_TRANSPARENT, 100, 200); + nmap["mix"] = add_node(C, ntree, SH_NODE_MIX_SHADER, 400, 300, "transparency"); + nmap["out"] = add_node(C, ntree, SH_NODE_OUTPUT_MATERIAL, 600, 300); + nmap["out"]->flag &= ~NODE_SELECT; + + add_link(ntree, nmap["emission"], 0, nmap["add"], 0); + add_link(ntree, nmap["main"], 0, nmap["add"], 1); + add_link(ntree, nmap["add"], 0, nmap["mix"], 1); + add_link(ntree, nmap["transparent"], 0, nmap["mix"], 2); + + add_link(ntree, nmap["mix"], 0, nmap["out"], 0); + // experimental, probably not used. + make_group(C, ntree, nmap); +#else + shader_node = add_node(SH_NODE_BSDF_PRINCIPLED, 0, 300, ""); + output_node = add_node(SH_NODE_OUTPUT_MATERIAL, 300, 300, ""); + add_link(shader_node, 0, output_node, 0); +#endif +} + +void MaterialNode::setShaderType() +{ +#if 0 + COLLADAFW::EffectCommon::ShaderType shader = ef->getShaderType(); + // Currently we only support PBR based shaders + // TODO: simulate the effects with PBR + + // blinn + if (shader == COLLADAFW::EffectCommon::SHADER_BLINN) { + ma->spec_shader = MA_SPEC_BLINN; + ma->spec = ef->getShininess().getFloatValue(); + } + // phong + else if (shader == COLLADAFW::EffectCommon::SHADER_PHONG) { + ma->spec_shader = MA_SPEC_PHONG; + ma->har = ef->getShininess().getFloatValue(); + } + // lambert + else if (shader == COLLADAFW::EffectCommon::SHADER_LAMBERT) { + ma->diff_shader = MA_DIFF_LAMBERT; + } + // default - lambert + else { + ma->diff_shader = MA_DIFF_LAMBERT; + fprintf(stderr, "Current shader type is not supported, default to lambert.\n"); + } +#endif +} + +// returns null if material already has a node tree +bNodeTree *MaterialNode::prepare_material_nodetree() +{ + if (material->nodetree) { + ntree = material->nodetree; + return NULL; + } + + material->nodetree = ntreeAddTree(NULL, "Shader Nodetree", "ShaderNodeTree"); + material->use_nodes = true; + ntree = material->nodetree; + return ntree; +} + +bNode *MaterialNode::add_node(int node_type, int locx, int locy, std::string label) +{ + bNode *node = nodeAddStaticNode(mContext, ntree, node_type); + if (node) { + if (label.length() > 0) { + strcpy(node->label, label.c_str()); + } + node->locx = locx; + node->locy = locy; + node->flag |= NODE_SELECT; + } + node_map[label] = node; + return node; +} + +void MaterialNode::add_link(bNode *from_node, int from_index, bNode *to_node, int to_index) +{ + bNodeSocket *from_socket = (bNodeSocket *)BLI_findlink(&from_node->outputs, from_index); + bNodeSocket *to_socket = (bNodeSocket *)BLI_findlink(&to_node->inputs, to_index); + + nodeAddLink(ntree, from_node, from_socket, to_node, to_socket); +} + +void MaterialNode::set_reflectivity(COLLADAFW::FloatOrParam &val) +{ + float reflectivity = val.getFloatValue(); + if (reflectivity >= 0) { + bNodeSocket *socket = nodeFindSocket(shader_node, SOCK_IN, "Metallic"); + ((bNodeSocketValueFloat *)socket->default_value)->value = reflectivity; + material->metallic = reflectivity; + } +} + +#if 0 +// needs rework to be done for 2.81 +void MaterialNode::set_shininess(COLLADAFW::FloatOrParam &val) +{ + float roughness = val.getFloatValue(); + if (roughness >= 0) { + bNodeSocket *socket = nodeFindSocket(shader_node, SOCK_IN, "Roughness"); + ((bNodeSocketValueFloat *)socket->default_value)->value = roughness; + } +} +#endif + +void MaterialNode::set_ior(COLLADAFW::FloatOrParam &val) +{ + float ior = val.getFloatValue(); + if (ior < 0) { + fprintf(stderr, + "IOR of negative value is not allowed for materials (using Blender default value " + "instead)"); + return; + } + + bNodeSocket *socket = nodeFindSocket(shader_node, SOCK_IN, "IOR"); + ((bNodeSocketValueFloat *)socket->default_value)->value = ior; +} + +void MaterialNode::set_alpha(COLLADAFW::EffectCommon::OpaqueMode mode, + COLLADAFW::ColorOrTexture &cot, + COLLADAFW::FloatOrParam &val) +{ + /* Handling the alpha value according to the Collada 1.4 reference guide + * see page 7-5 Determining Transparency (Opacity) + */ + + if (effect == nullptr) { + return; + } + + if (cot.isColor() || !cot.isValid()) { + // transparent_cot is either a color or not defined + + float transparent_alpha; + if (cot.isValid()) { + COLLADAFW::Color col = cot.getColor(); + transparent_alpha = col.getAlpha(); + } + else { + // no transparent color defined + transparent_alpha = 1; + } + + float transparency_alpha = val.getFloatValue(); + if (transparency_alpha < 0) { + // transparency is not defined + transparency_alpha = 1; // set to opaque + } + + float alpha = transparent_alpha * transparency_alpha; + if (mode == COLLADAFW::EffectCommon::RGB_ZERO) { + alpha = 1 - alpha; + } + + bNodeSocket *socket = nodeFindSocket(shader_node, SOCK_IN, "Alpha"); + ((bNodeSocketValueFloat *)socket->default_value)->value = alpha; + material->a = alpha; + } + else if (cot.isTexture()) { + int locy = -300 * (node_map.size() - 2); + add_texture_node(cot, -300, locy, "Alpha"); + } +} + +void MaterialNode::set_diffuse(COLLADAFW::ColorOrTexture &cot) +{ + int locy = -300 * (node_map.size() - 2); + if (cot.isColor()) { + COLLADAFW::Color col = cot.getColor(); + bNodeSocket *socket = nodeFindSocket(shader_node, SOCK_IN, "Base Color"); + float *fcol = (float *)socket->default_value; + + fcol[0] = material->r = col.getRed(); + fcol[1] = material->g = col.getGreen(); + fcol[2] = material->b = col.getBlue(); + fcol[3] = material->a = col.getAlpha(); + } + else if (cot.isTexture()) { + bNode *texture_node = add_texture_node(cot, -300, locy, "Base Color"); + if (texture_node != NULL) { + add_link(texture_node, 0, shader_node, 0); + } + } +} + +Image *MaterialNode::get_diffuse_image() +{ + bNode *shader = ntreeFindType(ntree, SH_NODE_BSDF_PRINCIPLED); + if (shader == nullptr) { + return nullptr; + } + + bNodeSocket *in_socket = nodeFindSocket(shader, SOCK_IN, "Base Color"); + if (in_socket == nullptr) { + return nullptr; + } + + bNodeLink *link = in_socket->link; + if (link == nullptr) { + return nullptr; + } + + bNode *texture = link->fromnode; + if (texture == nullptr) { + return nullptr; + } + + if (texture->type != SH_NODE_TEX_IMAGE) { + return nullptr; + } + + Image *image = (Image *)texture->id; + return image; +} + +static bNodeSocket *set_color(bNode *node, COLLADAFW::Color col) +{ + bNodeSocket *socket = (bNodeSocket *)BLI_findlink(&node->outputs, 0); + float *fcol = (float *)socket->default_value; + fcol[0] = col.getRed(); + fcol[1] = col.getGreen(); + fcol[2] = col.getBlue(); + + return socket; +} + +void MaterialNode::set_ambient(COLLADAFW::ColorOrTexture &cot) +{ + int locy = -300 * (node_map.size() - 2); + if (cot.isColor()) { + COLLADAFW::Color col = cot.getColor(); + bNode *node = add_node(SH_NODE_RGB, -300, locy, "Ambient"); + set_color(node, col); + // TODO: Connect node + } + // texture + else if (cot.isTexture()) { + add_texture_node(cot, -300, locy, "Ambient"); + // TODO: Connect node + } +} + +void MaterialNode::set_reflective(COLLADAFW::ColorOrTexture &cot) +{ + int locy = -300 * (node_map.size() - 2); + if (cot.isColor()) { + COLLADAFW::Color col = cot.getColor(); + bNode *node = add_node(SH_NODE_RGB, -300, locy, "Reflective"); + set_color(node, col); + // TODO: Connect node + } + // texture + else if (cot.isTexture()) { + add_texture_node(cot, -300, locy, "Reflective"); + // TODO: Connect node + } +} + +void MaterialNode::set_emission(COLLADAFW::ColorOrTexture &cot) +{ + int locy = -300 * (node_map.size() - 2); + if (cot.isColor()) { + COLLADAFW::Color col = cot.getColor(); + bNodeSocket *socket = nodeFindSocket(shader_node, SOCK_IN, "Emission"); + float *fcol = (float *)socket->default_value; + + fcol[0] = col.getRed(); + fcol[1] = col.getGreen(); + fcol[2] = col.getBlue(); + fcol[3] = col.getAlpha(); + } + else if (cot.isTexture()) { + bNode *texture_node = add_texture_node(cot, -300, locy, "Emission"); + if (texture_node != NULL) { + add_link(texture_node, 0, shader_node, 0); + } + } +} + +void MaterialNode::set_opacity(COLLADAFW::ColorOrTexture &cot) +{ + if (effect == nullptr) { + return; + } + + int locy = -300 * (node_map.size() - 2); + if (cot.isColor()) { + COLLADAFW::Color col = effect->getTransparent().getColor(); + float alpha = effect->getTransparency().getFloatValue(); + + if (col.isValid()) { + alpha *= col.getAlpha(); // Assuming A_ONE opaque mode + } + + bNodeSocket *socket = nodeFindSocket(shader_node, SOCK_IN, "Alpha"); + ((bNodeSocketValueFloat *)socket->default_value)->value = alpha; + } + // texture + else if (cot.isTexture()) { + add_texture_node(cot, -300, locy, "Alpha"); + // TODO: Connect node + } +} + +void MaterialNode::set_specular(COLLADAFW::ColorOrTexture &cot) +{ + int locy = -300 * (node_map.size() - 2); + if (cot.isColor()) { + COLLADAFW::Color col = cot.getColor(); + bNode *node = add_node(SH_NODE_RGB, -300, locy, "Specular"); + set_color(node, col); + // TODO: Connect node + } + // texture + else if (cot.isTexture()) { + add_texture_node(cot, -300, locy, "Specular"); + // TODO: Connect node + } +} + +bNode *MaterialNode::add_texture_node(COLLADAFW::ColorOrTexture &cot, + int locx, + int locy, + std::string label) +{ + if (effect == nullptr) { + return nullptr; + } + + UidImageMap &image_map = *uid_image_map; + + COLLADAFW::Texture ctex = cot.getTexture(); + + COLLADAFW::SamplerPointerArray &samp_array = effect->getSamplerPointerArray(); + COLLADAFW::Sampler *sampler = samp_array[ctex.getSamplerId()]; + + const COLLADAFW::UniqueId &ima_uid = sampler->getSourceImage(); + + if (image_map.find(ima_uid) == image_map.end()) { + fprintf(stderr, "Couldn't find an image by UID.\n"); + return NULL; + } + + Image *ima = image_map[ima_uid]; + bNode *texture_node = add_node(SH_NODE_TEX_IMAGE, locx, locy, label); + texture_node->id = &ima->id; + return texture_node; +} diff --git a/source/blender/io/collada/Materials.h b/source/blender/io/collada/Materials.h new file mode 100644 index 00000000000..0a4f2ee61a5 --- /dev/null +++ b/source/blender/io/collada/Materials.h @@ -0,0 +1,76 @@ +/* + * 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. + */ + +#ifndef __MATERIALS_H__ +#define __MATERIALS_H__ + +#include <map> +#include <string> + +extern "C" { +#include "BKE_context.h" +#include "BKE_node.h" +#include "BLI_listbase.h" +#include "DNA_material_types.h" +#include "DNA_node_types.h" +} + +#include "collada_utils.h" +#include "COLLADAFWEffectCommon.h" + +typedef std::map<std::string, bNode *> NodeMap; + +class MaterialNode { + + private: + bContext *mContext; + Material *material; + COLLADAFW::EffectCommon *effect; + UidImageMap *uid_image_map = nullptr; + KeyImageMap *key_image_map = nullptr; + + NodeMap node_map; + bNodeTree *ntree; + + bNode *shader_node; + bNode *output_node; + + bNodeTree *prepare_material_nodetree(); + bNode *add_node(int node_type, int locx, int locy, std::string label); + void add_link(bNode *from_node, int from_index, bNode *to_node, int to_index); + bNode *add_texture_node(COLLADAFW::ColorOrTexture &cot, int locx, int locy, std::string label); + void setShaderType(); + + public: + MaterialNode(bContext *C, COLLADAFW::EffectCommon *ef, Material *ma, UidImageMap &uid_image_map); + MaterialNode(bContext *C, Material *ma, KeyImageMap &key_image_map); + Image *get_diffuse_image(); + + void set_diffuse(COLLADAFW::ColorOrTexture &cot); + void set_specular(COLLADAFW::ColorOrTexture &cot); + void set_ambient(COLLADAFW::ColorOrTexture &cot); + void set_reflective(COLLADAFW::ColorOrTexture &cot); + void set_emission(COLLADAFW::ColorOrTexture &cot); + void set_opacity(COLLADAFW::ColorOrTexture &cot); + void set_reflectivity(COLLADAFW::FloatOrParam &val); + void set_shininess(COLLADAFW::FloatOrParam &val); + void set_ior(COLLADAFW::FloatOrParam &val); + void set_alpha(COLLADAFW::EffectCommon::OpaqueMode mode, + COLLADAFW::ColorOrTexture &cot, + COLLADAFW::FloatOrParam &val); +}; + +#endif /* __MATERIALS_H__ */ diff --git a/source/blender/io/collada/MeshImporter.cpp b/source/blender/io/collada/MeshImporter.cpp new file mode 100644 index 00000000000..bc6dd4202b1 --- /dev/null +++ b/source/blender/io/collada/MeshImporter.cpp @@ -0,0 +1,1208 @@ +/* + * 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 collada + */ + +#include <algorithm> +#include <iostream> + +/* COLLADABU_ASSERT, may be able to remove later */ +#include "COLLADABUPlatform.h" + +#include "COLLADAFWMeshPrimitive.h" +#include "COLLADAFWMeshVertexData.h" +#include "COLLADAFWPolygons.h" + +#include "MEM_guardedalloc.h" + +extern "C" { +#include "BKE_customdata.h" +#include "BKE_displist.h" +#include "BKE_global.h" +#include "BKE_lib_id.h" +#include "BKE_material.h" +#include "BKE_mesh.h" +#include "BKE_object.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_string.h" +#include "BLI_edgehash.h" +} + +#include "ArmatureImporter.h" +#include "MeshImporter.h" +#include "collada_utils.h" + +// get node name, or fall back to original id if not present (name is optional) +template<class T> static const std::string bc_get_dae_name(T *node) +{ + return node->getName().size() ? node->getName() : node->getOriginalId(); +} + +static const char *bc_primTypeToStr(COLLADAFW::MeshPrimitive::PrimitiveType type) +{ + switch (type) { + case COLLADAFW::MeshPrimitive::LINES: + return "LINES"; + case COLLADAFW::MeshPrimitive::LINE_STRIPS: + return "LINESTRIPS"; + case COLLADAFW::MeshPrimitive::POLYGONS: + return "POLYGONS"; + case COLLADAFW::MeshPrimitive::POLYLIST: + return "POLYLIST"; + case COLLADAFW::MeshPrimitive::TRIANGLES: + return "TRIANGLES"; + case COLLADAFW::MeshPrimitive::TRIANGLE_FANS: + return "TRIANGLE_FANS"; + case COLLADAFW::MeshPrimitive::TRIANGLE_STRIPS: + return "TRIANGLE_STRIPS"; + case COLLADAFW::MeshPrimitive::POINTS: + return "POINTS"; + case COLLADAFW::MeshPrimitive::UNDEFINED_PRIMITIVE_TYPE: + return "UNDEFINED_PRIMITIVE_TYPE"; + } + return "UNKNOWN"; +} + +static const char *bc_geomTypeToStr(COLLADAFW::Geometry::GeometryType type) +{ + switch (type) { + case COLLADAFW::Geometry::GEO_TYPE_MESH: + return "MESH"; + case COLLADAFW::Geometry::GEO_TYPE_SPLINE: + return "SPLINE"; + case COLLADAFW::Geometry::GEO_TYPE_CONVEX_MESH: + return "CONVEX_MESH"; + case COLLADAFW::Geometry::GEO_TYPE_UNKNOWN: + default: + return "UNKNOWN"; + } +} + +UVDataWrapper::UVDataWrapper(COLLADAFW::MeshVertexData &vdata) : mVData(&vdata) +{ +} + +#ifdef COLLADA_DEBUG +void WVDataWrapper::print() +{ + fprintf(stderr, "UVs:\n"); + switch (mVData->getType()) { + case COLLADAFW::MeshVertexData::DATA_TYPE_FLOAT: { + COLLADAFW::ArrayPrimitiveType<float> *values = mVData->getFloatValues(); + if (values->getCount()) { + for (int i = 0; i < values->getCount(); i += 2) { + fprintf(stderr, "%.1f, %.1f\n", (*values)[i], (*values)[i + 1]); + } + } + } break; + case COLLADAFW::MeshVertexData::DATA_TYPE_DOUBLE: { + COLLADAFW::ArrayPrimitiveType<double> *values = mVData->getDoubleValues(); + if (values->getCount()) { + for (int i = 0; i < values->getCount(); i += 2) { + fprintf(stderr, "%.1f, %.1f\n", (float)(*values)[i], (float)(*values)[i + 1]); + } + } + } break; + } + fprintf(stderr, "\n"); +} +#endif + +void UVDataWrapper::getUV(int uv_index, float *uv) +{ + int stride = mVData->getStride(0); + if (stride == 0) { + stride = 2; + } + + switch (mVData->getType()) { + case COLLADAFW::MeshVertexData::DATA_TYPE_FLOAT: { + COLLADAFW::ArrayPrimitiveType<float> *values = mVData->getFloatValues(); + if (values->empty()) { + return; + } + uv[0] = (*values)[uv_index * stride]; + uv[1] = (*values)[uv_index * stride + 1]; + + } break; + case COLLADAFW::MeshVertexData::DATA_TYPE_DOUBLE: { + COLLADAFW::ArrayPrimitiveType<double> *values = mVData->getDoubleValues(); + if (values->empty()) { + return; + } + uv[0] = (float)(*values)[uv_index * stride]; + uv[1] = (float)(*values)[uv_index * stride + 1]; + + } break; + case COLLADAFW::MeshVertexData::DATA_TYPE_UNKNOWN: + default: + fprintf(stderr, "MeshImporter.getUV(): unknown data type\n"); + } +} + +VCOLDataWrapper::VCOLDataWrapper(COLLADAFW::MeshVertexData &vdata) : mVData(&vdata) +{ +} + +void VCOLDataWrapper::get_vcol(int v_index, MLoopCol *mloopcol) +{ + int stride = mVData->getStride(0); + if (stride == 0) { + stride = 3; + } + + switch (mVData->getType()) { + case COLLADAFW::MeshVertexData::DATA_TYPE_FLOAT: { + COLLADAFW::ArrayPrimitiveType<float> *values = mVData->getFloatValues(); + if (values->empty() || values->getCount() <= (v_index * stride + 2)) { + return; // xxx need to create an error instead + } + + mloopcol->r = unit_float_to_uchar_clamp((*values)[v_index * stride]); + mloopcol->g = unit_float_to_uchar_clamp((*values)[v_index * stride + 1]); + mloopcol->b = unit_float_to_uchar_clamp((*values)[v_index * stride + 2]); + } break; + + case COLLADAFW::MeshVertexData::DATA_TYPE_DOUBLE: { + COLLADAFW::ArrayPrimitiveType<double> *values = mVData->getDoubleValues(); + if (values->empty() || values->getCount() <= (v_index * stride + 2)) { + return; // xxx need to create an error instead + } + + mloopcol->r = unit_float_to_uchar_clamp((*values)[v_index * stride]); + mloopcol->g = unit_float_to_uchar_clamp((*values)[v_index * stride + 1]); + mloopcol->b = unit_float_to_uchar_clamp((*values)[v_index * stride + 2]); + } break; + default: + fprintf(stderr, "VCOLDataWrapper.getvcol(): unknown data type\n"); + } +} + +MeshImporter::MeshImporter( + UnitConverter *unitconv, ArmatureImporter *arm, Main *bmain, Scene *sce, ViewLayer *view_layer) + : unitconverter(unitconv), + m_bmain(bmain), + scene(sce), + view_layer(view_layer), + armature_importer(arm) +{ + /* pass */ +} + +bool MeshImporter::set_poly_indices( + MPoly *mpoly, MLoop *mloop, int loop_index, unsigned int *indices, int loop_count) +{ + mpoly->loopstart = loop_index; + mpoly->totloop = loop_count; + bool broken_loop = false; + for (int index = 0; index < loop_count; index++) { + + /* Test if loop defines a hole */ + if (!broken_loop) { + for (int i = 0; i < index; i++) { + if (indices[i] == indices[index]) { + // duplicate index -> not good + broken_loop = true; + } + } + } + + mloop->v = indices[index]; + mloop++; + } + return broken_loop; +} + +void MeshImporter::set_vcol(MLoopCol *mlc, + VCOLDataWrapper &vob, + int loop_index, + COLLADAFW::IndexList &index_list, + int count) +{ + int index; + for (index = 0; index < count; index++, mlc++) { + int v_index = index_list.getIndex(index + loop_index); + vob.get_vcol(v_index, mlc); + } +} + +void MeshImporter::set_face_uv(MLoopUV *mloopuv, + UVDataWrapper &uvs, + int start_index, + COLLADAFW::IndexList &index_list, + int count) +{ + // per face vertex indices, this means for quad we have 4 indices, not 8 + COLLADAFW::UIntValuesArray &indices = index_list.getIndices(); + + for (int index = 0; index < count; index++) { + int uv_index = indices[index + start_index]; + uvs.getUV(uv_index, mloopuv[index].uv); + } +} + +#ifdef COLLADA_DEBUG +void MeshImporter::print_index_list(COLLADAFW::IndexList &index_list) +{ + fprintf(stderr, "Index list for \"%s\":\n", index_list.getName().c_str()); + for (int i = 0; i < index_list.getIndicesCount(); i += 2) { + fprintf(stderr, "%u, %u\n", index_list.getIndex(i), index_list.getIndex(i + 1)); + } + fprintf(stderr, "\n"); +} +#endif + +/* checks if mesh has supported primitive types: lines, polylist, triangles, triangle_fans */ +bool MeshImporter::is_nice_mesh(COLLADAFW::Mesh *mesh) +{ + COLLADAFW::MeshPrimitiveArray &prim_arr = mesh->getMeshPrimitives(); + + const std::string &name = bc_get_dae_name(mesh); + + for (unsigned int i = 0; i < prim_arr.getCount(); i++) { + + COLLADAFW::MeshPrimitive *mp = prim_arr[i]; + COLLADAFW::MeshPrimitive::PrimitiveType type = mp->getPrimitiveType(); + + const char *type_str = bc_primTypeToStr(type); + + // OpenCollada passes POLYGONS type for <polylist> + if (type == COLLADAFW::MeshPrimitive::POLYLIST || type == COLLADAFW::MeshPrimitive::POLYGONS) { + + COLLADAFW::Polygons *mpvc = (COLLADAFW::Polygons *)mp; + COLLADAFW::Polygons::VertexCountArray &vca = mpvc->getGroupedVerticesVertexCountArray(); + + int hole_count = 0; + int nonface_count = 0; + + for (unsigned int j = 0; j < vca.getCount(); j++) { + int count = vca[j]; + if (abs(count) < 3) { + nonface_count++; + } + + if (count < 0) { + hole_count++; + } + } + + if (hole_count > 0) { + fprintf(stderr, + "WARNING: Primitive %s in %s: %d holes not imported (unsupported)\n", + type_str, + name.c_str(), + hole_count); + } + + if (nonface_count > 0) { + fprintf(stderr, + "WARNING: Primitive %s in %s: %d faces with vertex count < 3 (rejected)\n", + type_str, + name.c_str(), + nonface_count); + } + } + + else if (type == COLLADAFW::MeshPrimitive::LINES) { + // TODO: Add Checker for line syntax here + } + + else if (type != COLLADAFW::MeshPrimitive::TRIANGLES && + type != COLLADAFW::MeshPrimitive::TRIANGLE_FANS) { + fprintf(stderr, "ERROR: Primitive type %s is not supported.\n", type_str); + return false; + } + } + + return true; +} + +void MeshImporter::read_vertices(COLLADAFW::Mesh *mesh, Mesh *me) +{ + // vertices + COLLADAFW::MeshVertexData &pos = mesh->getPositions(); + if (pos.empty()) { + return; + } + + int stride = pos.getStride(0); + if (stride == 0) { + stride = 3; + } + + me->totvert = pos.getFloatValues()->getCount() / stride; + me->mvert = (MVert *)CustomData_add_layer(&me->vdata, CD_MVERT, CD_CALLOC, NULL, me->totvert); + + MVert *mvert; + int i; + + for (i = 0, mvert = me->mvert; i < me->totvert; i++, mvert++) { + get_vector(mvert->co, pos, i, stride); + } +} + +// ===================================================================== +// condition 1: The Primitive has normals +// condition 2: The number of normals equals the number of faces. +// return true if both conditions apply. +// return false otherwise. +// ===================================================================== +bool MeshImporter::primitive_has_useable_normals(COLLADAFW::MeshPrimitive *mp) +{ + + bool has_useable_normals = false; + + int normals_count = mp->getNormalIndices().getCount(); + if (normals_count > 0) { + int index_count = mp->getPositionIndices().getCount(); + if (index_count == normals_count) { + has_useable_normals = true; + } + else { + fprintf(stderr, + "Warning: Number of normals %d is different from the number of vertices %d, " + "skipping normals\n", + normals_count, + index_count); + } + } + + return has_useable_normals; +} + +// ===================================================================== +// Assume that only TRIANGLES, TRIANGLE_FANS, POLYLIST and POLYGONS +// have faces. (to be verified) +// ===================================================================== +bool MeshImporter::primitive_has_faces(COLLADAFW::MeshPrimitive *mp) +{ + + bool has_faces = false; + int type = mp->getPrimitiveType(); + switch (type) { + case COLLADAFW::MeshPrimitive::TRIANGLES: + case COLLADAFW::MeshPrimitive::TRIANGLE_FANS: + case COLLADAFW::MeshPrimitive::POLYLIST: + case COLLADAFW::MeshPrimitive::POLYGONS: { + has_faces = true; + break; + } + default: { + has_faces = false; + break; + } + } + return has_faces; +} + +static std::string extract_vcolname(const COLLADAFW::String &collada_id) +{ + std::string colname = collada_id; + int spos = colname.find("-mesh-colors-"); + if (spos != std::string::npos) { + colname = colname.substr(spos + 13); + } + return colname; +} + +// ================================================================= +// Return the number of faces by summing up +// the facecounts of the parts. +// hint: This is done because mesh->getFacesCount() does +// count loose edges as extra faces, which is not what we want here. +// ================================================================= +void MeshImporter::allocate_poly_data(COLLADAFW::Mesh *collada_mesh, Mesh *me) +{ + COLLADAFW::MeshPrimitiveArray &prim_arr = collada_mesh->getMeshPrimitives(); + int total_poly_count = 0; + int total_loop_count = 0; + + // collect edge_count and face_count from all parts + for (int i = 0; i < prim_arr.getCount(); i++) { + COLLADAFW::MeshPrimitive *mp = prim_arr[i]; + int type = mp->getPrimitiveType(); + switch (type) { + case COLLADAFW::MeshPrimitive::TRIANGLES: + case COLLADAFW::MeshPrimitive::TRIANGLE_FANS: + case COLLADAFW::MeshPrimitive::POLYLIST: + case COLLADAFW::MeshPrimitive::POLYGONS: { + COLLADAFW::Polygons *mpvc = (COLLADAFW::Polygons *)mp; + size_t prim_poly_count = mpvc->getFaceCount(); + + size_t prim_loop_count = 0; + for (int index = 0; index < prim_poly_count; index++) { + int vcount = get_vertex_count(mpvc, index); + if (vcount > 0) { + prim_loop_count += vcount; + total_poly_count++; + } + else { + // TODO: this is a hole and not another polygon! + } + } + + total_loop_count += prim_loop_count; + + break; + } + default: + break; + } + } + + // Add the data containers + if (total_poly_count > 0) { + me->totpoly = total_poly_count; + me->totloop = total_loop_count; + me->mpoly = (MPoly *)CustomData_add_layer(&me->pdata, CD_MPOLY, CD_CALLOC, NULL, me->totpoly); + me->mloop = (MLoop *)CustomData_add_layer(&me->ldata, CD_MLOOP, CD_CALLOC, NULL, me->totloop); + + unsigned int totuvset = collada_mesh->getUVCoords().getInputInfosArray().getCount(); + for (int i = 0; i < totuvset; i++) { + if (collada_mesh->getUVCoords().getLength(i) == 0) { + totuvset = 0; + break; + } + } + + if (totuvset > 0) { + for (int i = 0; i < totuvset; i++) { + COLLADAFW::MeshVertexData::InputInfos *info = + collada_mesh->getUVCoords().getInputInfosArray()[i]; + COLLADAFW::String &uvname = info->mName; + // Allocate space for UV_data + CustomData_add_layer_named( + &me->ldata, CD_MLOOPUV, CD_DEFAULT, NULL, me->totloop, uvname.c_str()); + } + // activate the first uv map + me->mloopuv = (MLoopUV *)CustomData_get_layer_n(&me->ldata, CD_MLOOPUV, 0); + } + + int totcolset = collada_mesh->getColors().getInputInfosArray().getCount(); + if (totcolset > 0) { + for (int i = 0; i < totcolset; i++) { + COLLADAFW::MeshVertexData::InputInfos *info = + collada_mesh->getColors().getInputInfosArray()[i]; + COLLADAFW::String colname = extract_vcolname(info->mName); + CustomData_add_layer_named( + &me->ldata, CD_MLOOPCOL, CD_DEFAULT, NULL, me->totloop, colname.c_str()); + } + me->mloopcol = (MLoopCol *)CustomData_get_layer_n(&me->ldata, CD_MLOOPCOL, 0); + } + } +} + +unsigned int MeshImporter::get_vertex_count(COLLADAFW::Polygons *mp, int index) +{ + int type = mp->getPrimitiveType(); + int result; + switch (type) { + case COLLADAFW::MeshPrimitive::TRIANGLES: + case COLLADAFW::MeshPrimitive::TRIANGLE_FANS: { + result = 3; + break; + } + case COLLADAFW::MeshPrimitive::POLYLIST: + case COLLADAFW::MeshPrimitive::POLYGONS: { + result = mp->getGroupedVerticesVertexCountArray()[index]; + break; + } + default: { + result = -1; + break; + } + } + return result; +} + +unsigned int MeshImporter::get_loose_edge_count(COLLADAFW::Mesh *mesh) +{ + COLLADAFW::MeshPrimitiveArray &prim_arr = mesh->getMeshPrimitives(); + int loose_edge_count = 0; + + // collect edge_count and face_count from all parts + for (int i = 0; i < prim_arr.getCount(); i++) { + COLLADAFW::MeshPrimitive *mp = prim_arr[i]; + int type = mp->getPrimitiveType(); + switch (type) { + case COLLADAFW::MeshPrimitive::LINES: { + size_t prim_totface = mp->getFaceCount(); + loose_edge_count += prim_totface; + break; + } + default: + break; + } + } + return loose_edge_count; +} + +// ================================================================= +// This function is copied from source/blender/editors/mesh/mesh_data.c +// +// TODO: (As discussed with sergey-) : +// Maybe move this function to blenderkernel/intern/mesh.c +// and add definition to BKE_mesh.c +// ================================================================= +void MeshImporter::mesh_add_edges(Mesh *mesh, int len) +{ + CustomData edata; + MEdge *medge; + int totedge; + + if (len == 0) { + return; + } + + totedge = mesh->totedge + len; + + /* update customdata */ + CustomData_copy(&mesh->edata, &edata, CD_MASK_MESH.emask, CD_DEFAULT, totedge); + CustomData_copy_data(&mesh->edata, &edata, 0, 0, mesh->totedge); + + if (!CustomData_has_layer(&edata, CD_MEDGE)) { + CustomData_add_layer(&edata, CD_MEDGE, CD_CALLOC, NULL, totedge); + } + + CustomData_free(&mesh->edata, mesh->totedge); + mesh->edata = edata; + BKE_mesh_update_customdata_pointers(mesh, false); /* new edges don't change tessellation */ + + /* set default flags */ + medge = &mesh->medge[mesh->totedge]; + for (int i = 0; i < len; i++, medge++) { + medge->flag = ME_EDGEDRAW | ME_EDGERENDER | SELECT; + } + + mesh->totedge = totedge; +} + +// ================================================================= +// Read all loose edges. +// Important: This function assumes that all edges from existing +// faces have already been generated and added to me->medge +// So this function MUST be called after read_faces() (see below) +// ================================================================= +void MeshImporter::read_lines(COLLADAFW::Mesh *mesh, Mesh *me) +{ + unsigned int loose_edge_count = get_loose_edge_count(mesh); + if (loose_edge_count > 0) { + + unsigned int face_edge_count = me->totedge; + /* unsigned int total_edge_count = loose_edge_count + face_edge_count; */ /* UNUSED */ + + mesh_add_edges(me, loose_edge_count); + MEdge *med = me->medge + face_edge_count; + + COLLADAFW::MeshPrimitiveArray &prim_arr = mesh->getMeshPrimitives(); + + for (int index = 0; index < prim_arr.getCount(); index++) { + COLLADAFW::MeshPrimitive *mp = prim_arr[index]; + + int type = mp->getPrimitiveType(); + if (type == COLLADAFW::MeshPrimitive::LINES) { + unsigned int edge_count = mp->getFaceCount(); + unsigned int *indices = mp->getPositionIndices().getData(); + + for (int j = 0; j < edge_count; j++, med++) { + med->bweight = 0; + med->crease = 0; + med->flag |= ME_LOOSEEDGE; + med->v1 = indices[2 * j]; + med->v2 = indices[2 * j + 1]; + } + } + } + } +} + +// ======================================================================= +// Read all faces from TRIANGLES, TRIANGLE_FANS, POLYLIST, POLYGON +// Important: This function MUST be called before read_lines() +// Otherwise we will loose all edges from faces (see read_lines() above) +// +// TODO: import uv set names +// ======================================================================== +void MeshImporter::read_polys(COLLADAFW::Mesh *collada_mesh, Mesh *me) +{ + unsigned int i; + + allocate_poly_data(collada_mesh, me); + + UVDataWrapper uvs(collada_mesh->getUVCoords()); + VCOLDataWrapper vcol(collada_mesh->getColors()); + + MPoly *mpoly = me->mpoly; + MLoop *mloop = me->mloop; + int loop_index = 0; + + MaterialIdPrimitiveArrayMap mat_prim_map; + + COLLADAFW::MeshPrimitiveArray &prim_arr = collada_mesh->getMeshPrimitives(); + COLLADAFW::MeshVertexData &nor = collada_mesh->getNormals(); + + for (i = 0; i < prim_arr.getCount(); i++) { + + COLLADAFW::MeshPrimitive *mp = prim_arr[i]; + + // faces + size_t prim_totpoly = mp->getFaceCount(); + unsigned int *position_indices = mp->getPositionIndices().getData(); + unsigned int *normal_indices = mp->getNormalIndices().getData(); + + bool mp_has_normals = primitive_has_useable_normals(mp); + bool mp_has_faces = primitive_has_faces(mp); + + int collada_meshtype = mp->getPrimitiveType(); + + // since we cannot set mpoly->mat_nr here, we store a portion of me->mpoly in Primitive + Primitive prim = {mpoly, 0}; + + // If MeshPrimitive is TRIANGLE_FANS we split it into triangles + // The first trifan vertex will be the first vertex in every triangle + // XXX The proper function of TRIANGLE_FANS is not tested!!! + // XXX In particular the handling of the normal_indices looks very wrong to me + if (collada_meshtype == COLLADAFW::MeshPrimitive::TRIANGLE_FANS) { + unsigned int grouped_vertex_count = mp->getGroupedVertexElementsCount(); + for (unsigned int group_index = 0; group_index < grouped_vertex_count; group_index++) { + unsigned int first_vertex = position_indices[0]; // Store first trifan vertex + unsigned int first_normal = normal_indices[0]; // Store first trifan vertex normal + unsigned int vertex_count = mp->getGroupedVerticesVertexCount(group_index); + + for (unsigned int vertex_index = 0; vertex_index < vertex_count - 2; vertex_index++) { + // For each triangle store indices of its 3 vertices + unsigned int triangle_vertex_indices[3] = { + first_vertex, position_indices[1], position_indices[2]}; + set_poly_indices(mpoly, mloop, loop_index, triangle_vertex_indices, 3); + + if (mp_has_normals) { // vertex normals, same implementation as for the triangles + // the same for vertces normals + unsigned int vertex_normal_indices[3] = { + first_normal, normal_indices[1], normal_indices[2]}; + if (!is_flat_face(vertex_normal_indices, nor, 3)) { + mpoly->flag |= ME_SMOOTH; + } + normal_indices++; + } + + mpoly++; + mloop += 3; + loop_index += 3; + prim.totpoly++; + } + + // Moving cursor to the next triangle fan. + if (mp_has_normals) { + normal_indices += 2; + } + + position_indices += 2; + } + } + + if (collada_meshtype == COLLADAFW::MeshPrimitive::POLYLIST || + collada_meshtype == COLLADAFW::MeshPrimitive::POLYGONS || + collada_meshtype == COLLADAFW::MeshPrimitive::TRIANGLES) { + COLLADAFW::Polygons *mpvc = (COLLADAFW::Polygons *)mp; + unsigned int start_index = 0; + + COLLADAFW::IndexListArray &index_list_array_uvcoord = mp->getUVCoordIndicesArray(); + COLLADAFW::IndexListArray &index_list_array_vcolor = mp->getColorIndicesArray(); + + int invalid_loop_holes = 0; + for (unsigned int j = 0; j < prim_totpoly; j++) { + + // Vertices in polygon: + int vcount = get_vertex_count(mpvc, j); + if (vcount < 0) { + continue; // TODO: add support for holes + } + + bool broken_loop = set_poly_indices(mpoly, mloop, loop_index, position_indices, vcount); + if (broken_loop) { + invalid_loop_holes += 1; + } + + for (unsigned int uvset_index = 0; uvset_index < index_list_array_uvcoord.getCount(); + uvset_index++) { + // get mtface by face index and uv set index + COLLADAFW::IndexList &index_list = *index_list_array_uvcoord[uvset_index]; + MLoopUV *mloopuv = (MLoopUV *)CustomData_get_layer_named( + &me->ldata, CD_MLOOPUV, index_list.getName().c_str()); + if (mloopuv == NULL) { + fprintf(stderr, + "Collada import: Mesh [%s] : Unknown reference to TEXCOORD [#%s].\n", + me->id.name, + index_list.getName().c_str()); + } + else { + set_face_uv(mloopuv + loop_index, + uvs, + start_index, + *index_list_array_uvcoord[uvset_index], + vcount); + } + } + + if (mp_has_normals) { + if (!is_flat_face(normal_indices, nor, vcount)) { + mpoly->flag |= ME_SMOOTH; + } + } + + if (mp->hasColorIndices()) { + int vcolor_count = index_list_array_vcolor.getCount(); + + for (unsigned int vcolor_index = 0; vcolor_index < vcolor_count; vcolor_index++) { + + COLLADAFW::IndexList &color_index_list = *mp->getColorIndices(vcolor_index); + COLLADAFW::String colname = extract_vcolname(color_index_list.getName()); + MLoopCol *mloopcol = (MLoopCol *)CustomData_get_layer_named( + &me->ldata, CD_MLOOPCOL, colname.c_str()); + if (mloopcol == NULL) { + fprintf(stderr, + "Collada import: Mesh [%s] : Unknown reference to VCOLOR [#%s].\n", + me->id.name, + color_index_list.getName().c_str()); + } + else { + set_vcol(mloopcol + loop_index, vcol, start_index, color_index_list, vcount); + } + } + } + + mpoly++; + mloop += vcount; + loop_index += vcount; + start_index += vcount; + prim.totpoly++; + + if (mp_has_normals) { + normal_indices += vcount; + } + + position_indices += vcount; + } + + if (invalid_loop_holes > 0) { + fprintf(stderr, + "Collada import: Mesh [%s] : contains %d unsupported loops (holes).\n", + me->id.name, + invalid_loop_holes); + } + } + + else if (collada_meshtype == COLLADAFW::MeshPrimitive::LINES) { + continue; // read the lines later after all the rest is done + } + + if (mp_has_faces) { + mat_prim_map[mp->getMaterialId()].push_back(prim); + } + } + + geom_uid_mat_mapping_map[collada_mesh->getUniqueId()] = mat_prim_map; +} + +void MeshImporter::get_vector(float v[3], COLLADAFW::MeshVertexData &arr, int i, int stride) +{ + i *= stride; + + switch (arr.getType()) { + case COLLADAFW::MeshVertexData::DATA_TYPE_FLOAT: { + COLLADAFW::ArrayPrimitiveType<float> *values = arr.getFloatValues(); + if (values->empty()) { + return; + } + + v[0] = (*values)[i++]; + v[1] = (*values)[i++]; + if (stride >= 3) { + v[2] = (*values)[i]; + } + else { + v[2] = 0.0f; + } + + } break; + case COLLADAFW::MeshVertexData::DATA_TYPE_DOUBLE: { + COLLADAFW::ArrayPrimitiveType<double> *values = arr.getDoubleValues(); + if (values->empty()) { + return; + } + + v[0] = (float)(*values)[i++]; + v[1] = (float)(*values)[i++]; + if (stride >= 3) { + v[2] = (float)(*values)[i]; + } + else { + v[2] = 0.0f; + } + } break; + default: + break; + } +} + +bool MeshImporter::is_flat_face(unsigned int *nind, COLLADAFW::MeshVertexData &nor, int count) +{ + float a[3], b[3]; + + get_vector(a, nor, *nind, 3); + normalize_v3(a); + + nind++; + + for (int i = 1; i < count; i++, nind++) { + get_vector(b, nor, *nind, 3); + normalize_v3(b); + + float dp = dot_v3v3(a, b); + + if (dp < 0.99999f || dp > 1.00001f) { + return false; + } + } + + return true; +} + +Object *MeshImporter::get_object_by_geom_uid(const COLLADAFW::UniqueId &geom_uid) +{ + if (uid_object_map.find(geom_uid) != uid_object_map.end()) { + return uid_object_map[geom_uid]; + } + return NULL; +} + +Mesh *MeshImporter::get_mesh_by_geom_uid(const COLLADAFW::UniqueId &mesh_uid) +{ + if (uid_mesh_map.find(mesh_uid) != uid_mesh_map.end()) { + return uid_mesh_map[mesh_uid]; + } + return NULL; +} + +std::string *MeshImporter::get_geometry_name(const std::string &mesh_name) +{ + if (this->mesh_geom_map.find(mesh_name) != this->mesh_geom_map.end()) { + return &this->mesh_geom_map[mesh_name]; + } + return NULL; +} + +/** + * this function checks if both objects have the same + * materials assigned to Object (in the same order) + * returns true if condition matches, otherwise false; + */ +static bool bc_has_same_material_configuration(Object *ob1, Object *ob2) +{ + if (ob1->totcol != ob2->totcol) { + return false; // not same number of materials + } + if (ob1->totcol == 0) { + return false; // no material at all + } + + for (int index = 0; index < ob1->totcol; index++) { + if (ob1->matbits[index] != ob2->matbits[index]) { + return false; // shouldn't happen + } + if (ob1->matbits[index] == 0) { + return false; // shouldn't happen + } + if (ob1->mat[index] != ob2->mat[index]) { + return false; // different material assignment + } + } + return true; +} + +/** + * + * Caution here: This code assumes that all materials are assigned to Object + * and no material is assigned to Data. + * That is true right after the objects have been imported. + * + */ +static void bc_copy_materials_to_data(Object *ob, Mesh *me) +{ + for (int index = 0; index < ob->totcol; index++) { + ob->matbits[index] = 0; + me->mat[index] = ob->mat[index]; + } +} + +/** + * + * Remove all references to materials from the object + * + */ +static void bc_remove_materials_from_object(Object *ob, Mesh *me) +{ + for (int index = 0; index < ob->totcol; index++) { + ob->matbits[index] = 0; + ob->mat[index] = NULL; + } +} + +/** + * Returns the list of Users of the given Mesh object. + * Note: This function uses the object user flag to control + * which objects have already been processed. + */ +std::vector<Object *> MeshImporter::get_all_users_of(Mesh *reference_mesh) +{ + std::vector<Object *> mesh_users; + for (std::vector<Object *>::iterator it = imported_objects.begin(); it != imported_objects.end(); + ++it) { + Object *ob = (*it); + if (bc_is_marked(ob)) { + bc_remove_mark(ob); + Mesh *me = (Mesh *)ob->data; + if (me == reference_mesh) { + mesh_users.push_back(ob); + } + } + } + return mesh_users; +} + +/** + * + * During import all materials have been assigned to Object. + * Now we iterate over the imported objects and optimize + * the assignments as follows: + * + * for each imported geometry: + * if number of users is 1: + * get the user (object) + * move the materials from Object to Data + * else: + * determine which materials are assigned to the first user + * check if all other users have the same materials in the same order + * if the check is positive: + * Add the materials of the first user to the geometry + * adjust all other users accordingly. + * + */ +void MeshImporter::optimize_material_assignements() +{ + for (std::vector<Object *>::iterator it = imported_objects.begin(); it != imported_objects.end(); + ++it) { + Object *ob = (*it); + Mesh *me = (Mesh *)ob->data; + if (ID_REAL_USERS(&me->id) == 1) { + bc_copy_materials_to_data(ob, me); + bc_remove_materials_from_object(ob, me); + bc_remove_mark(ob); + } + else if (ID_REAL_USERS(&me->id) > 1) { + bool can_move = true; + std::vector<Object *> mesh_users = get_all_users_of(me); + if (mesh_users.size() > 1) { + Object *ref_ob = mesh_users[0]; + for (int index = 1; index < mesh_users.size(); index++) { + if (!bc_has_same_material_configuration(ref_ob, mesh_users[index])) { + can_move = false; + break; + } + } + if (can_move) { + bc_copy_materials_to_data(ref_ob, me); + for (int index = 0; index < mesh_users.size(); index++) { + Object *object = mesh_users[index]; + bc_remove_materials_from_object(object, me); + bc_remove_mark(object); + } + } + } + } + } +} + +/** + * We do not know in advance which objects will share geometries. + * And we do not know either if the objects which share geometries + * come along with different materials. So we first create the objects + * and assign the materials to Object, then in a later cleanup we decide + * which materials shall be moved to the created geometries. Also see + * optimize_material_assignements() above. + */ +void MeshImporter::assign_material_to_geom( + COLLADAFW::MaterialBinding cmaterial, + std::map<COLLADAFW::UniqueId, Material *> &uid_material_map, + Object *ob, + const COLLADAFW::UniqueId *geom_uid, + short mat_index) +{ + const COLLADAFW::UniqueId &ma_uid = cmaterial.getReferencedMaterial(); + + // do we know this material? + if (uid_material_map.find(ma_uid) == uid_material_map.end()) { + + fprintf(stderr, "Cannot find material by UID.\n"); + return; + } + + // first time we get geom_uid, ma_uid pair. Save for later check. + materials_mapped_to_geom.insert( + std::pair<COLLADAFW::UniqueId, COLLADAFW::UniqueId>(*geom_uid, ma_uid)); + + Material *ma = uid_material_map[ma_uid]; + + // Attention! This temporarily assigns material to object on purpose! + // See note above. + ob->actcol = 0; + BKE_object_material_assign(m_bmain, ob, ma, mat_index + 1, BKE_MAT_ASSIGN_OBJECT); + + MaterialIdPrimitiveArrayMap &mat_prim_map = geom_uid_mat_mapping_map[*geom_uid]; + COLLADAFW::MaterialId mat_id = cmaterial.getMaterialId(); + + // assign material indices to mesh faces + if (mat_prim_map.find(mat_id) != mat_prim_map.end()) { + + std::vector<Primitive> &prims = mat_prim_map[mat_id]; + + std::vector<Primitive>::iterator it; + + for (it = prims.begin(); it != prims.end(); it++) { + Primitive &prim = *it; + MPoly *mpoly = prim.mpoly; + + for (int i = 0; i < prim.totpoly; i++, mpoly++) { + mpoly->mat_nr = mat_index; + } + } + } +} + +Object *MeshImporter::create_mesh_object( + COLLADAFW::Node *node, + COLLADAFW::InstanceGeometry *geom, + bool isController, + std::map<COLLADAFW::UniqueId, Material *> &uid_material_map) +{ + const COLLADAFW::UniqueId *geom_uid = &geom->getInstanciatedObjectId(); + + // check if node instantiates controller or geometry + if (isController) { + + geom_uid = armature_importer->get_geometry_uid(*geom_uid); + + if (!geom_uid) { + fprintf(stderr, "Couldn't find a mesh UID by controller's UID.\n"); + return NULL; + } + } + else { + + if (uid_mesh_map.find(*geom_uid) == uid_mesh_map.end()) { + // this could happen if a mesh was not created + // (e.g. if it contains unsupported geometry) + fprintf(stderr, "Couldn't find a mesh by UID.\n"); + return NULL; + } + } + if (!uid_mesh_map[*geom_uid]) { + return NULL; + } + + // name Object + const std::string &id = node->getName().size() ? node->getName() : node->getOriginalId(); + const char *name = (id.length()) ? id.c_str() : NULL; + + // add object + Object *ob = bc_add_object(m_bmain, scene, view_layer, OB_MESH, name); + bc_set_mark(ob); // used later for material assignment optimization + + // store object pointer for ArmatureImporter + uid_object_map[*geom_uid] = ob; + imported_objects.push_back(ob); + + // replace ob->data freeing the old one + Mesh *old_mesh = (Mesh *)ob->data; + Mesh *new_mesh = uid_mesh_map[*geom_uid]; + + BKE_mesh_assign_object(m_bmain, ob, new_mesh); + BKE_mesh_calc_normals(new_mesh); + + /* Because BKE_mesh_assign_object would have already decreased it... */ + id_us_plus(&old_mesh->id); + + BKE_id_free_us(m_bmain, old_mesh); + + COLLADAFW::MaterialBindingArray &mat_array = geom->getMaterialBindings(); + + // loop through geom's materials + for (unsigned int i = 0; i < mat_array.getCount(); i++) { + + if (mat_array[i].getReferencedMaterial().isValid()) { + assign_material_to_geom(mat_array[i], uid_material_map, ob, geom_uid, i); + } + else { + fprintf(stderr, "invalid referenced material for %s\n", mat_array[i].getName().c_str()); + } + } + + // clean up the mesh + BKE_mesh_validate((Mesh *)ob->data, false, false); + + return ob; +} + +// create a mesh storing a pointer in a map so it can be retrieved later by geometry UID +bool MeshImporter::write_geometry(const COLLADAFW::Geometry *geom) +{ + + if (geom->getType() != COLLADAFW::Geometry::GEO_TYPE_MESH) { + // TODO: report warning + fprintf(stderr, "Mesh type %s is not supported\n", bc_geomTypeToStr(geom->getType())); + return true; + } + + COLLADAFW::Mesh *mesh = (COLLADAFW::Mesh *)geom; + + if (!is_nice_mesh(mesh)) { + fprintf(stderr, "Ignoring mesh %s\n", bc_get_dae_name(mesh).c_str()); + return true; + } + + const std::string &str_geom_id = mesh->getName().size() ? mesh->getName() : + mesh->getOriginalId(); + Mesh *me = BKE_mesh_add(m_bmain, (char *)str_geom_id.c_str()); + id_us_min(&me->id); // is already 1 here, but will be set later in BKE_mesh_assign_object + + // store the Mesh pointer to link it later with an Object + // mesh_geom_map needed to map mesh to its geometry name (for shape key naming) + this->uid_mesh_map[mesh->getUniqueId()] = me; + this->mesh_geom_map[std::string(me->id.name)] = str_geom_id; + + read_vertices(mesh, me); + read_polys(mesh, me); + BKE_mesh_calc_edges(me, false, false); + // read_lines() must be called after the face edges have been generated. + // Otherwise the loose edges will be silently deleted again. + read_lines(mesh, me); + + return true; +} diff --git a/source/blender/io/collada/MeshImporter.h b/source/blender/io/collada/MeshImporter.h new file mode 100644 index 00000000000..9517587013d --- /dev/null +++ b/source/blender/io/collada/MeshImporter.h @@ -0,0 +1,182 @@ +/* + * 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 collada + */ + +#ifndef __MESHIMPORTER_H__ +#define __MESHIMPORTER_H__ + +#include <map> +#include <vector> + +#include "COLLADAFWIndexList.h" +#include "COLLADAFWPolygons.h" +#include "COLLADAFWInstanceGeometry.h" +#include "COLLADAFWMaterialBinding.h" +#include "COLLADAFWMesh.h" +#include "COLLADAFWMeshVertexData.h" +#include "COLLADAFWNode.h" +#include "COLLADAFWTextureCoordinateBinding.h" +#include "COLLADAFWTypes.h" +#include "COLLADAFWUniqueId.h" + +#include "ArmatureImporter.h" +#include "collada_utils.h" + +extern "C" { +#include "BLI_edgehash.h" +#include "DNA_material_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +} + +/* only for ArmatureImporter to "see" MeshImporter::get_object_by_geom_uid */ +class MeshImporterBase { + public: + virtual Object *get_object_by_geom_uid(const COLLADAFW::UniqueId &geom_uid) = 0; + virtual Mesh *get_mesh_by_geom_uid(const COLLADAFW::UniqueId &mesh_uid) = 0; + virtual std::string *get_geometry_name(const std::string &mesh_name) = 0; +}; + +class UVDataWrapper { + COLLADAFW::MeshVertexData *mVData; + + public: + UVDataWrapper(COLLADAFW::MeshVertexData &vdata); + +#ifdef COLLADA_DEBUG + void print(); +#endif + + void getUV(int uv_index, float *uv); +}; + +class VCOLDataWrapper { + COLLADAFW::MeshVertexData *mVData; + + public: + VCOLDataWrapper(COLLADAFW::MeshVertexData &vdata); + void get_vcol(int v_index, MLoopCol *mloopcol); +}; + +class MeshImporter : public MeshImporterBase { + private: + UnitConverter *unitconverter; + + Main *m_bmain; + Scene *scene; + ViewLayer *view_layer; + + ArmatureImporter *armature_importer; + + std::map<std::string, std::string> mesh_geom_map; /* needed for correct shape key naming */ + std::map<COLLADAFW::UniqueId, Mesh *> uid_mesh_map; /* geometry unique id-to-mesh map */ + std::map<COLLADAFW::UniqueId, Object *> uid_object_map; /* geom uid-to-object */ + std::vector<Object *> imported_objects; /* list of imported objects */ + + /* this structure is used to assign material indices to polygons + * it holds a portion of Mesh faces and corresponds to a DAE primitive list + * (<triangles>, <polylist>, etc.) */ + struct Primitive { + MPoly *mpoly; + unsigned int totpoly; + }; + typedef std::map<COLLADAFW::MaterialId, std::vector<Primitive>> MaterialIdPrimitiveArrayMap; + /* crazy name! */ + std::map<COLLADAFW::UniqueId, MaterialIdPrimitiveArrayMap> geom_uid_mat_mapping_map; + /* < materials that have already been mapped to a geometry. + * A pair/of geom uid and mat uid, one geometry can have several materials */ + std::multimap<COLLADAFW::UniqueId, COLLADAFW::UniqueId> materials_mapped_to_geom; + + bool set_poly_indices( + MPoly *mpoly, MLoop *mloop, int loop_index, unsigned int *indices, int loop_count); + + void set_face_uv(MLoopUV *mloopuv, + UVDataWrapper &uvs, + int loop_index, + COLLADAFW::IndexList &index_list, + int count); + + void set_vcol(MLoopCol *mloopcol, + VCOLDataWrapper &vob, + int loop_index, + COLLADAFW::IndexList &index_list, + int count); + +#ifdef COLLADA_DEBUG + void print_index_list(COLLADAFW::IndexList &index_list); +#endif + + bool is_nice_mesh(COLLADAFW::Mesh *mesh); + + void read_vertices(COLLADAFW::Mesh *mesh, Mesh *me); + + bool primitive_has_useable_normals(COLLADAFW::MeshPrimitive *mp); + bool primitive_has_faces(COLLADAFW::MeshPrimitive *mp); + + static void mesh_add_edges(Mesh *mesh, int len); + + unsigned int get_loose_edge_count(COLLADAFW::Mesh *mesh); + + CustomData create_edge_custom_data(EdgeHash *eh); + + void allocate_poly_data(COLLADAFW::Mesh *collada_mesh, Mesh *me); + + /* TODO: import uv set names */ + void read_polys(COLLADAFW::Mesh *mesh, Mesh *me); + void read_lines(COLLADAFW::Mesh *mesh, Mesh *me); + unsigned int get_vertex_count(COLLADAFW::Polygons *mp, int index); + + void get_vector(float v[3], COLLADAFW::MeshVertexData &arr, int i, int stride); + + bool is_flat_face(unsigned int *nind, COLLADAFW::MeshVertexData &nor, int count); + + std::vector<Object *> get_all_users_of(Mesh *reference_mesh); + + public: + MeshImporter(UnitConverter *unitconv, + ArmatureImporter *arm, + Main *bmain, + Scene *sce, + ViewLayer *view_layer); + + virtual Object *get_object_by_geom_uid(const COLLADAFW::UniqueId &geom_uid); + + virtual Mesh *get_mesh_by_geom_uid(const COLLADAFW::UniqueId &geom_uid); + + void optimize_material_assignements(); + + void assign_material_to_geom(COLLADAFW::MaterialBinding cmaterial, + std::map<COLLADAFW::UniqueId, Material *> &uid_material_map, + Object *ob, + const COLLADAFW::UniqueId *geom_uid, + short mat_index); + + Object *create_mesh_object(COLLADAFW::Node *node, + COLLADAFW::InstanceGeometry *geom, + bool isController, + std::map<COLLADAFW::UniqueId, Material *> &uid_material_map); + + /* create a mesh storing a pointer in a map so it can be retrieved later by geometry UID */ + bool write_geometry(const COLLADAFW::Geometry *geom); + std::string *get_geometry_name(const std::string &mesh_name); +}; + +#endif diff --git a/source/blender/io/collada/SceneExporter.cpp b/source/blender/io/collada/SceneExporter.cpp new file mode 100644 index 00000000000..42901bd2a4a --- /dev/null +++ b/source/blender/io/collada/SceneExporter.cpp @@ -0,0 +1,242 @@ +/* + * 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 collada + */ + +extern "C" { +#include "BLI_utildefines.h" +#include "BKE_collection.h" +#include "BKE_object.h" +#include "BLI_listbase.h" +#include "BKE_lib_id.h" +} + +#include "SceneExporter.h" +#include "collada_utils.h" +#include "BCSampleData.h" + +void SceneExporter::exportScene() +{ + Scene *scene = blender_context.get_scene(); + + /* <library_visual_scenes> <visual_scene> */ + std::string name = id_name(scene); + openVisualScene(translate_id(name), encode_xml(name)); + exportHierarchy(); + closeVisualScene(); + closeLibrary(); +} + +void SceneExporter::exportHierarchy() +{ + LinkNode *node; + ColladaBaseNodes base_objects; + + /* Ensure all objects in the export_set are marked */ + for (node = this->export_settings.get_export_set(); node; node = node->next) { + Object *ob = (Object *)node->link; + ob->id.tag |= LIB_TAG_DOIT; + } + + /* Now find all exportable base objects (highest in export hierarchy) */ + for (node = this->export_settings.get_export_set(); node; node = node->next) { + Object *ob = (Object *)node->link; + if (this->export_settings.is_export_root(ob)) { + switch (ob->type) { + case OB_MESH: + case OB_CAMERA: + case OB_LAMP: + case OB_EMPTY: + case OB_GPENCIL: + case OB_ARMATURE: + base_objects.add(ob); + break; + } + } + } + + /* And now export the base objects: */ + for (int index = 0; index < base_objects.size(); index++) { + Object *ob = base_objects.get(index); + writeNode(ob); + if (bc_is_marked(ob)) { + bc_remove_mark(ob); + } + } +} + +void SceneExporter::writeNodeList(std::vector<Object *> &child_objects, Object *parent) +{ + /* TODO: Handle the case where a parent is not exported + * Actually i am not even sure if this can be done at all + * in a good way. + * I really prefer to enforce the export of hidden + * elements in an object hierarchy. When the children of + * the hidden elements are exported as well. */ + for (int i = 0; i < child_objects.size(); i++) { + Object *child = child_objects[i]; + writeNode(child); + if (bc_is_marked(child)) { + bc_remove_mark(child); + } + } +} + +void SceneExporter::writeNode(Object *ob) +{ + ViewLayer *view_layer = blender_context.get_view_layer(); + + std::vector<Object *> child_objects; + bc_get_children(child_objects, ob, view_layer); + bool can_export = bc_is_in_Export_set(this->export_settings.get_export_set(), ob, view_layer); + + /* Add associated armature first if available */ + bool armature_exported = false; + Object *ob_arm = bc_get_assigned_armature(ob); + + if (ob_arm != NULL) { + armature_exported = bc_is_in_Export_set( + this->export_settings.get_export_set(), ob_arm, view_layer); + if (armature_exported && bc_is_marked(ob_arm)) { + writeNode(ob_arm); + bc_remove_mark(ob_arm); + armature_exported = true; + } + } + + if (can_export) { + COLLADASW::Node colladaNode(mSW); + colladaNode.setNodeId(translate_id(id_name(ob))); + colladaNode.setNodeName(encode_xml(id_name(ob))); + colladaNode.setType(COLLADASW::Node::NODE); + + colladaNode.start(); + if (ob->type == OB_MESH && armature_exported) { + /* for skinned mesh we write obmat in <bind_shape_matrix> */ + TransformWriter::add_node_transform_identity(colladaNode, this->export_settings); + } + else { + TransformWriter::add_node_transform_ob(colladaNode, ob, this->export_settings); + } + + /* <instance_geometry> */ + if (ob->type == OB_MESH) { + bool instance_controller_created = false; + if (armature_exported) { + instance_controller_created = arm_exporter->add_instance_controller(ob); + } + if (!instance_controller_created) { + COLLADASW::InstanceGeometry instGeom(mSW); + instGeom.setUrl(COLLADASW::URI( + COLLADABU::Utils::EMPTY_STRING, + get_geometry_id(ob, this->export_settings.get_use_object_instantiation()))); + instGeom.setName(encode_xml(id_name(ob))); + InstanceWriter::add_material_bindings( + instGeom.getBindMaterial(), ob, this->export_settings.get_active_uv_only()); + instGeom.add(); + } + } + + /* <instance_controller> */ + else if (ob->type == OB_ARMATURE) { + arm_exporter->add_armature_bones(ob, view_layer, this, child_objects); + } + + /* <instance_camera> */ + else if (ob->type == OB_CAMERA) { + COLLADASW::InstanceCamera instCam( + mSW, COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, get_camera_id(ob))); + instCam.add(); + } + + /* <instance_light> */ + else if (ob->type == OB_LAMP) { + COLLADASW::InstanceLight instLa( + mSW, COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, get_light_id(ob))); + instLa.add(); + } + + /* empty object */ + else if (ob->type == OB_EMPTY) { /* TODO: handle groups (OB_DUPLICOLLECTION */ + if ((ob->transflag & OB_DUPLICOLLECTION) == OB_DUPLICOLLECTION && ob->instance_collection) { + Collection *collection = ob->instance_collection; + /* printf("group detected '%s'\n", group->id.name + 2); */ + FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (collection, object) { + printf("\t%s\n", object->id.name); + } + FOREACH_COLLECTION_OBJECT_RECURSIVE_END; + } + + if (BLI_listbase_is_empty(&ob->constraints) == false) { + bConstraint *con = (bConstraint *)ob->constraints.first; + while (con) { + std::string con_name(encode_xml(con->name)); + std::string con_tag = con_name + "_constraint"; + printf("%s\n", con_name.c_str()); + printf("%s\n\n", con_tag.c_str()); + colladaNode.addExtraTechniqueChildParameter("blender", con_tag, "type", con->type); + colladaNode.addExtraTechniqueChildParameter("blender", con_tag, "enforce", con->enforce); + colladaNode.addExtraTechniqueChildParameter("blender", con_tag, "flag", con->flag); + colladaNode.addExtraTechniqueChildParameter( + "blender", con_tag, "headtail", con->headtail); + colladaNode.addExtraTechniqueChildParameter( + "blender", con_tag, "lin_error", con->lin_error); + colladaNode.addExtraTechniqueChildParameter( + "blender", con_tag, "own_space", con->ownspace); + colladaNode.addExtraTechniqueChildParameter( + "blender", con_tag, "rot_error", con->rot_error); + colladaNode.addExtraTechniqueChildParameter( + "blender", con_tag, "tar_space", con->tarspace); + colladaNode.addExtraTechniqueChildParameter( + "blender", con_tag, "lin_error", con->lin_error); + + /* not ideal: add the target object name as another parameter. + * No real mapping in the .dae + * Need support for multiple target objects also. */ + const bConstraintTypeInfo *cti = BKE_constraint_typeinfo_get(con); + ListBase targets = {NULL, NULL}; + if (cti && cti->get_constraint_targets) { + + bConstraintTarget *ct; + Object *obtar; + + cti->get_constraint_targets(con, &targets); + + for (ct = (bConstraintTarget *)targets.first; ct; ct = ct->next) { + obtar = ct->tar; + std::string tar_id((obtar) ? id_name(obtar) : ""); + colladaNode.addExtraTechniqueChildParameter("blender", con_tag, "target_id", tar_id); + } + + if (cti->flush_constraint_targets) { + cti->flush_constraint_targets(con, &targets, 1); + } + } + + con = con->next; + } + } + } + bc_remove_mark(ob); + writeNodeList(child_objects, ob); + colladaNode.end(); + } + else { + writeNodeList(child_objects, ob); + } +} diff --git a/source/blender/io/collada/SceneExporter.h b/source/blender/io/collada/SceneExporter.h new file mode 100644 index 00000000000..a61d045ad5d --- /dev/null +++ b/source/blender/io/collada/SceneExporter.h @@ -0,0 +1,117 @@ +/* + * 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 collada + */ + +#ifndef __SCENEEXPORTER_H__ +#define __SCENEEXPORTER_H__ + +#include <stdlib.h> +#include <stdio.h> +#include <math.h> + +extern "C" { +#include "DNA_scene_types.h" +#include "DNA_object_types.h" +#include "DNA_collection_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_mesh_types.h" +#include "DNA_image_types.h" +#include "DNA_material_types.h" +#include "DNA_texture_types.h" +#include "DNA_anim_types.h" +#include "DNA_action_types.h" +#include "DNA_curve_types.h" +#include "DNA_constraint_types.h" +#include "DNA_armature_types.h" +#include "DNA_modifier_types.h" +#include "DNA_userdef_types.h" + +#include "BKE_fcurve.h" +#include "BKE_animsys.h" +#include "BLI_path_util.h" +#include "BKE_constraint.h" +#include "BLI_fileops.h" +#include "ED_keyframing.h" +} + +#include "COLLADASWAsset.h" +#include "COLLADASWLibraryVisualScenes.h" +#include "COLLADASWNode.h" +#include "COLLADASWSource.h" +#include "COLLADASWInstanceGeometry.h" +#include "COLLADASWInputList.h" +#include "COLLADASWPrimitves.h" +#include "COLLADASWVertices.h" +#include "COLLADASWLibraryAnimations.h" +#include "COLLADASWLibraryImages.h" +#include "COLLADASWLibraryEffects.h" +#include "COLLADASWImage.h" +#include "COLLADASWEffectProfile.h" +#include "COLLADASWColorOrTexture.h" +#include "COLLADASWParamTemplate.h" +#include "COLLADASWParamBase.h" +#include "COLLADASWSurfaceInitOption.h" +#include "COLLADASWSampler.h" +#include "COLLADASWScene.h" +#include "COLLADASWTechnique.h" +#include "COLLADASWTexture.h" +#include "COLLADASWLibraryMaterials.h" +#include "COLLADASWBindMaterial.h" +#include "COLLADASWInstanceCamera.h" +#include "COLLADASWInstanceLight.h" +#include "COLLADASWConstants.h" +#include "COLLADASWLibraryControllers.h" +#include "COLLADASWInstanceController.h" +#include "COLLADASWInstanceNode.h" +#include "COLLADASWBaseInputElement.h" + +#include "ArmatureExporter.h" +#include "ExportSettings.h" + +extern void bc_get_children(std::vector<Object *> &child_set, Object *ob, ViewLayer *view_layer); + +class SceneExporter : COLLADASW::LibraryVisualScenes, + protected TransformWriter, + protected InstanceWriter { + public: + SceneExporter(BlenderContext &blender_context, + COLLADASW::StreamWriter *sw, + ArmatureExporter *arm, + BCExportSettings &export_settings) + : COLLADASW::LibraryVisualScenes(sw), + blender_context(blender_context), + arm_exporter(arm), + export_settings(export_settings) + { + } + + void exportScene(); + + private: + BlenderContext &blender_context; + friend class ArmatureExporter; + ArmatureExporter *arm_exporter; + BCExportSettings &export_settings; + + void exportHierarchy(); + void writeNodeList(std::vector<Object *> &child_objects, Object *parent); + void writeNode(Object *ob); +}; + +#endif diff --git a/source/blender/io/collada/SkinInfo.cpp b/source/blender/io/collada/SkinInfo.cpp new file mode 100644 index 00000000000..d8804a1e831 --- /dev/null +++ b/source/blender/io/collada/SkinInfo.cpp @@ -0,0 +1,357 @@ +/* + * 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 collada + */ + +#include <algorithm> + +#if !defined(WIN32) +# include <stdint.h> +#endif + +/* COLLADABU_ASSERT, may be able to remove later */ +#include "COLLADABUPlatform.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_compiler_attrs.h" + +#include "DNA_armature_types.h" +#include "DNA_modifier_types.h" +#include "DNA_scene_types.h" + +#include "BKE_action.h" +#include "BKE_object.h" +#include "BKE_object_deform.h" + +#include "ED_mesh.h" +#include "ED_object.h" + +#include "SkinInfo.h" +#include "collada_utils.h" + +/* use name, or fall back to original id if name not present (name is optional) */ +template<class T> static const char *bc_get_joint_name(T *node) +{ + const std::string &id = node->getName(); + return id.size() ? id.c_str() : node->getOriginalId().c_str(); +} + +/* This is used to store data passed in write_controller_data. + * Arrays from COLLADAFW::SkinControllerData lose ownership, so do this class members + * so that arrays don't get freed until we free them explicitly. */ +SkinInfo::SkinInfo() +{ + /* pass */ +} + +SkinInfo::SkinInfo(const SkinInfo &skin) + : weights(skin.weights), + joint_data(skin.joint_data), + unit_converter(skin.unit_converter), + ob_arm(skin.ob_arm), + controller_uid(skin.controller_uid), + parent(skin.parent) +{ + copy_m4_m4(bind_shape_matrix, (float(*)[4])skin.bind_shape_matrix); + + transfer_uint_array_data_const(skin.joints_per_vertex, joints_per_vertex); + transfer_uint_array_data_const(skin.weight_indices, weight_indices); + transfer_int_array_data_const(skin.joint_indices, joint_indices); +} + +SkinInfo::SkinInfo(UnitConverter *conv) : unit_converter(conv), ob_arm(NULL), parent(NULL) +{ +} + +/* nobody owns the data after this, so it should be freed manually with releaseMemory */ +template<class T> void SkinInfo::transfer_array_data(T &src, T &dest) +{ + dest.setData(src.getData(), src.getCount()); + src.yieldOwnerShip(); + dest.yieldOwnerShip(); +} + +/* when src is const we cannot src.yieldOwnerShip, this is used by copy constructor */ +void SkinInfo::transfer_int_array_data_const(const COLLADAFW::IntValuesArray &src, + COLLADAFW::IntValuesArray &dest) +{ + dest.setData((int *)src.getData(), src.getCount()); + dest.yieldOwnerShip(); +} + +void SkinInfo::transfer_uint_array_data_const(const COLLADAFW::UIntValuesArray &src, + COLLADAFW::UIntValuesArray &dest) +{ + dest.setData((unsigned int *)src.getData(), src.getCount()); + dest.yieldOwnerShip(); +} + +void SkinInfo::borrow_skin_controller_data(const COLLADAFW::SkinControllerData *skin) +{ + transfer_array_data((COLLADAFW::UIntValuesArray &)skin->getJointsPerVertex(), joints_per_vertex); + transfer_array_data((COLLADAFW::UIntValuesArray &)skin->getWeightIndices(), weight_indices); + transfer_array_data((COLLADAFW::IntValuesArray &)skin->getJointIndices(), joint_indices); + // transfer_array_data(skin->getWeights(), weights); + + /* cannot transfer data for FloatOrDoubleArray, copy values manually */ + const COLLADAFW::FloatOrDoubleArray &weight = skin->getWeights(); + for (unsigned int i = 0; i < weight.getValuesCount(); i++) { + weights.push_back(bc_get_float_value(weight, i)); + } + + unit_converter->dae_matrix_to_mat4_(bind_shape_matrix, skin->getBindShapeMatrix()); +} + +void SkinInfo::free() +{ + joints_per_vertex.releaseMemory(); + weight_indices.releaseMemory(); + joint_indices.releaseMemory(); + // weights.releaseMemory(); +} + +/* using inverse bind matrices to construct armature + * it is safe to invert them to get the original matrices + * because if they are inverse matrices, they can be inverted */ +void SkinInfo::add_joint(const COLLADABU::Math::Matrix4 &matrix) +{ + JointData jd; + unit_converter->dae_matrix_to_mat4_(jd.inv_bind_mat, matrix); + joint_data.push_back(jd); +} + +void SkinInfo::set_controller(const COLLADAFW::SkinController *co) +{ + controller_uid = co->getUniqueId(); + + /* fill in joint UIDs */ + const COLLADAFW::UniqueIdArray &joint_uids = co->getJoints(); + for (unsigned int i = 0; i < joint_uids.getCount(); i++) { + joint_data[i].joint_uid = joint_uids[i]; + + /* store armature pointer */ + // JointData& jd = joint_index_to_joint_info_map[i]; + // jd.ob_arm = ob_arm; + + /* now we'll be able to get inv bind matrix from joint id */ + // joint_id_to_joint_index_map[joint_ids[i]] = i; + } +} + +/* called from write_controller */ +Object *SkinInfo::create_armature(Main *bmain, Scene *scene, ViewLayer *view_layer) +{ + ob_arm = bc_add_object(bmain, scene, view_layer, OB_ARMATURE, NULL); + return ob_arm; +} + +Object *SkinInfo::set_armature(Object *ob_arm) +{ + if (this->ob_arm) { + return this->ob_arm; + } + + this->ob_arm = ob_arm; + return ob_arm; +} + +bool SkinInfo::get_joint_inv_bind_matrix(float inv_bind_mat[4][4], COLLADAFW::Node *node) +{ + const COLLADAFW::UniqueId &uid = node->getUniqueId(); + std::vector<JointData>::iterator it; + for (it = joint_data.begin(); it != joint_data.end(); it++) { + if ((*it).joint_uid == uid) { + copy_m4_m4(inv_bind_mat, (*it).inv_bind_mat); + return true; + } + } + + return false; +} + +Object *SkinInfo::BKE_armature_from_object() +{ + return ob_arm; +} + +const COLLADAFW::UniqueId &SkinInfo::get_controller_uid() +{ + return controller_uid; +} + +/* check if this skin controller references a joint or any descendant of it + * + * some nodes may not be referenced by SkinController, + * in this case to determine if the node belongs to this armature, + * we need to search down the tree */ +bool SkinInfo::uses_joint_or_descendant(COLLADAFW::Node *node) +{ + const COLLADAFW::UniqueId &uid = node->getUniqueId(); + std::vector<JointData>::iterator it; + for (it = joint_data.begin(); it != joint_data.end(); it++) { + if ((*it).joint_uid == uid) { + return true; + } + } + + COLLADAFW::NodePointerArray &children = node->getChildNodes(); + for (unsigned int i = 0; i < children.getCount(); i++) { + if (uses_joint_or_descendant(children[i])) { + return true; + } + } + + return false; +} + +void SkinInfo::link_armature(bContext *C, + Object *ob, + std::map<COLLADAFW::UniqueId, COLLADAFW::Node *> &joint_by_uid, + TransformReader *tm) +{ + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + + ModifierData *md = ED_object_modifier_add(NULL, bmain, scene, ob, NULL, eModifierType_Armature); + ArmatureModifierData *amd = (ArmatureModifierData *)md; + amd->object = ob_arm; + +#if 1 + /* XXX Why do we enforce objects to be children of Armatures if they weren't so before ?*/ + if (!BKE_object_is_child_recursive(ob_arm, ob)) { + bc_set_parent(ob, ob_arm, C); + } +#else + Object workob; + ob->parent = ob_arm; + ob->partype = PAROBJECT; + + BKE_object_workob_calc_parent(scene, ob, &workob); + invert_m4_m4(ob->parentinv, workob.obmat); + + DEG_id_tag_update(&obn->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); +#endif + copy_m4_m4(ob->obmat, bind_shape_matrix); + BKE_object_apply_mat4(ob, ob->obmat, 0, 0); + + amd->deformflag = ARM_DEF_VGROUP; + + /* create all vertex groups */ + std::vector<JointData>::iterator it; + int joint_index; + for (it = joint_data.begin(), joint_index = 0; it != joint_data.end(); it++, joint_index++) { + const char *name = "Group"; + + /* skip joints that have invalid UID */ + if ((*it).joint_uid == COLLADAFW::UniqueId::INVALID) { + continue; + } + + /* name group by joint node name */ + + if (joint_by_uid.find((*it).joint_uid) != joint_by_uid.end()) { + name = bc_get_joint_name(joint_by_uid[(*it).joint_uid]); + } + + BKE_object_defgroup_add_name(ob, name); + } + + /* <vcount> - number of joints per vertex - joints_per_vertex + * <v> - [[bone index, weight index] * joints per vertex] * vertices - weight indices + * ^ bone index can be -1 meaning weight toward bind shape, how to express this in Blender? + * + * for each vertex in weight indices + * for each bone index in vertex + * add vertex to group at group index + * treat group index -1 specially + * + * get def group by index with BLI_findlink */ + + for (unsigned int vertex = 0, weight = 0; vertex < joints_per_vertex.getCount(); vertex++) { + + unsigned int limit = weight + joints_per_vertex[vertex]; + for (; weight < limit; weight++) { + int joint = joint_indices[weight], joint_weight = weight_indices[weight]; + + /* -1 means "weight towards the bind shape", we just don't assign it to any group */ + if (joint != -1) { + bDeformGroup *def = (bDeformGroup *)BLI_findlink(&ob->defbase, joint); + + ED_vgroup_vert_add(ob, def, vertex, weights[joint_weight], WEIGHT_REPLACE); + } + } + } +} + +bPoseChannel *SkinInfo::get_pose_channel_from_node(COLLADAFW::Node *node) +{ + return BKE_pose_channel_find_name(ob_arm->pose, bc_get_joint_name(node)); +} + +void SkinInfo::set_parent(Object *_parent) +{ + parent = _parent; +} + +Object *SkinInfo::get_parent() +{ + return parent; +} + +void SkinInfo::find_root_joints(const std::vector<COLLADAFW::Node *> &root_joints, + std::map<COLLADAFW::UniqueId, COLLADAFW::Node *> &joint_by_uid, + std::vector<COLLADAFW::Node *> &result) +{ + std::vector<COLLADAFW::Node *>::const_iterator it; + /* for each root_joint */ + for (it = root_joints.begin(); it != root_joints.end(); it++) { + COLLADAFW::Node *root = *it; + std::vector<JointData>::iterator ji; + /* for each joint_data in this skin */ + for (ji = joint_data.begin(); ji != joint_data.end(); ji++) { + if (joint_by_uid.find((*ji).joint_uid) != joint_by_uid.end()) { + /* get joint node from joint map */ + COLLADAFW::Node *joint = joint_by_uid[(*ji).joint_uid]; + + /* find if joint node is in the tree belonging to the root_joint */ + if (find_node_in_tree(joint, root)) { + if (std::find(result.begin(), result.end(), root) == result.end()) { + result.push_back(root); + } + } + } + } + } +} + +bool SkinInfo::find_node_in_tree(COLLADAFW::Node *node, COLLADAFW::Node *tree_root) +{ + if (node == tree_root) { + return true; + } + + COLLADAFW::NodePointerArray &children = tree_root->getChildNodes(); + for (unsigned int i = 0; i < children.getCount(); i++) { + if (find_node_in_tree(node, children[i])) { + return true; + } + } + + return false; +} diff --git a/source/blender/io/collada/SkinInfo.h b/source/blender/io/collada/SkinInfo.h new file mode 100644 index 00000000000..255d6d9b1f3 --- /dev/null +++ b/source/blender/io/collada/SkinInfo.h @@ -0,0 +1,130 @@ +/* + * 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 collada + */ + +#ifndef __SKININFO_H__ +#define __SKININFO_H__ + +#include <map> +#include <vector> + +#include "COLLADAFWUniqueId.h" +#include "COLLADAFWTypes.h" +#include "COLLADAFWNode.h" +#include "COLLADAFWSkinController.h" +#include "COLLADAFWSkinControllerData.h" + +#include "DNA_object_types.h" +#include "BKE_context.h" + +#include "TransformReader.h" +#include "collada_internal.h" + +// This is used to store data passed in write_controller_data. +// Arrays from COLLADAFW::SkinControllerData lose ownership, so do this class members +// so that arrays don't get freed until we free them explicitly. +class SkinInfo { + private: + // to build armature bones from inverse bind matrices + struct JointData { + float inv_bind_mat[4][4]; // joint inverse bind matrix + COLLADAFW::UniqueId joint_uid; // joint node UID + // Object *ob_arm; // armature object + }; + + float bind_shape_matrix[4][4]; + + // data from COLLADAFW::SkinControllerData, each array should be freed + COLLADAFW::UIntValuesArray joints_per_vertex; + COLLADAFW::UIntValuesArray weight_indices; + COLLADAFW::IntValuesArray joint_indices; + // COLLADAFW::FloatOrDoubleArray weights; + std::vector<float> weights; + + std::vector<JointData> joint_data; // index to this vector is joint index + + UnitConverter *unit_converter; + + Object *ob_arm; + COLLADAFW::UniqueId controller_uid; + Object *parent; + + public: + SkinInfo(); + SkinInfo(const SkinInfo &skin); + SkinInfo(UnitConverter *conv); + + // nobody owns the data after this, so it should be freed manually with releaseMemory + template<typename T> void transfer_array_data(T &src, T &dest); + + // when src is const we cannot src.yieldOwnerShip, this is used by copy constructor + void transfer_int_array_data_const(const COLLADAFW::IntValuesArray &src, + COLLADAFW::IntValuesArray &dest); + + void transfer_uint_array_data_const(const COLLADAFW::UIntValuesArray &src, + COLLADAFW::UIntValuesArray &dest); + + void borrow_skin_controller_data(const COLLADAFW::SkinControllerData *skin); + + void free(); + + // using inverse bind matrices to construct armature + // it is safe to invert them to get the original matrices + // because if they are inverse matrices, they can be inverted + void add_joint(const COLLADABU::Math::Matrix4 &matrix); + + void set_controller(const COLLADAFW::SkinController *co); + + // called from write_controller + Object *create_armature(Main *bmain, Scene *scene, ViewLayer *view_layer); + + Object *set_armature(Object *ob_arm); + + bool get_joint_inv_bind_matrix(float inv_bind_mat[4][4], COLLADAFW::Node *node); + + Object *BKE_armature_from_object(); + + const COLLADAFW::UniqueId &get_controller_uid(); + + // check if this skin controller references a joint or any descendant of it + // + // some nodes may not be referenced by SkinController, + // in this case to determine if the node belongs to this armature, + // we need to search down the tree + bool uses_joint_or_descendant(COLLADAFW::Node *node); + + void link_armature(bContext *C, + Object *ob, + std::map<COLLADAFW::UniqueId, COLLADAFW::Node *> &joint_by_uid, + TransformReader *tm); + + bPoseChannel *get_pose_channel_from_node(COLLADAFW::Node *node); + + void set_parent(Object *_parent); + + Object *get_parent(); + + void find_root_joints(const std::vector<COLLADAFW::Node *> &root_joints, + std::map<COLLADAFW::UniqueId, COLLADAFW::Node *> &joint_by_uid, + std::vector<COLLADAFW::Node *> &result); + + bool find_node_in_tree(COLLADAFW::Node *node, COLLADAFW::Node *tree_root); +}; + +#endif diff --git a/source/blender/io/collada/TransformReader.cpp b/source/blender/io/collada/TransformReader.cpp new file mode 100644 index 00000000000..8ee31f80405 --- /dev/null +++ b/source/blender/io/collada/TransformReader.cpp @@ -0,0 +1,151 @@ +/* + * 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 collada + */ + +/* COLLADABU_ASSERT, may be able to remove later */ +#include "COLLADABUPlatform.h" + +#include "TransformReader.h" + +TransformReader::TransformReader(UnitConverter *conv) : unit_converter(conv) +{ + /* pass */ +} + +void TransformReader::get_node_mat(float mat[4][4], + COLLADAFW::Node *node, + std::map<COLLADAFW::UniqueId, Animation> *animation_map, + Object *ob) +{ + get_node_mat(mat, node, animation_map, ob, NULL); +} + +void TransformReader::get_node_mat(float mat[4][4], + COLLADAFW::Node *node, + std::map<COLLADAFW::UniqueId, Animation> *animation_map, + Object *ob, + float parent_mat[4][4]) +{ + float cur[4][4]; + float copy[4][4]; + + unit_m4(mat); + + for (unsigned int i = 0; i < node->getTransformations().getCount(); i++) { + + COLLADAFW::Transformation *tm = node->getTransformations()[i]; + COLLADAFW::Transformation::TransformationType type = tm->getTransformationType(); + + switch (type) { + case COLLADAFW::Transformation::MATRIX: + // When matrix AND Trans/Rot/Scale are defined for a node, + // then this is considered as redundant information. + // So if we find a Matrix we use that and return. + dae_matrix_to_mat4(tm, mat); + if (parent_mat) { + mul_m4_m4m4(mat, parent_mat, mat); + } + return; + case COLLADAFW::Transformation::TRANSLATE: + dae_translate_to_mat4(tm, cur); + break; + case COLLADAFW::Transformation::ROTATE: + dae_rotate_to_mat4(tm, cur); + break; + case COLLADAFW::Transformation::SCALE: + dae_scale_to_mat4(tm, cur); + break; + case COLLADAFW::Transformation::LOOKAT: + fprintf(stderr, "|! LOOKAT transformations are not supported yet.\n"); + break; + case COLLADAFW::Transformation::SKEW: + fprintf(stderr, "|! SKEW transformations are not supported yet.\n"); + break; + } + + copy_m4_m4(copy, mat); + mul_m4_m4m4(mat, copy, cur); + + if (animation_map) { + // AnimationList that drives this Transformation + const COLLADAFW::UniqueId &anim_list_id = tm->getAnimationList(); + + // store this so later we can link animation data with ob + Animation anim = {ob, node, tm}; + (*animation_map)[anim_list_id] = anim; + } + } + + if (parent_mat) { + mul_m4_m4m4(mat, parent_mat, mat); + } +} + +void TransformReader::dae_rotate_to_mat4(COLLADAFW::Transformation *tm, float m[4][4]) +{ + COLLADAFW::Rotate *ro = (COLLADAFW::Rotate *)tm; + COLLADABU::Math::Vector3 &axis = ro->getRotationAxis(); + const float angle = (float)DEG2RAD(ro->getRotationAngle()); + const float ax[] = {(float)axis[0], (float)axis[1], (float)axis[2]}; + // float quat[4]; + // axis_angle_to_quat(quat, axis, angle); + // quat_to_mat4(m, quat); + axis_angle_to_mat4(m, ax, angle); +} + +void TransformReader::dae_translate_to_mat4(COLLADAFW::Transformation *tm, float m[4][4]) +{ + COLLADAFW::Translate *tra = (COLLADAFW::Translate *)tm; + COLLADABU::Math::Vector3 &t = tra->getTranslation(); + + unit_m4(m); + + m[3][0] = (float)t[0]; + m[3][1] = (float)t[1]; + m[3][2] = (float)t[2]; +} + +void TransformReader::dae_scale_to_mat4(COLLADAFW::Transformation *tm, float m[4][4]) +{ + COLLADABU::Math::Vector3 &s = ((COLLADAFW::Scale *)tm)->getScale(); + float size[3] = {(float)s[0], (float)s[1], (float)s[2]}; + size_to_mat4(m, size); +} + +void TransformReader::dae_matrix_to_mat4(COLLADAFW::Transformation *tm, float m[4][4]) +{ + unit_converter->dae_matrix_to_mat4_(m, ((COLLADAFW::Matrix *)tm)->getMatrix()); +} + +void TransformReader::dae_translate_to_v3(COLLADAFW::Transformation *tm, float v[3]) +{ + dae_vector3_to_v3(((COLLADAFW::Translate *)tm)->getTranslation(), v); +} + +void TransformReader::dae_scale_to_v3(COLLADAFW::Transformation *tm, float v[3]) +{ + dae_vector3_to_v3(((COLLADAFW::Scale *)tm)->getScale(), v); +} + +void TransformReader::dae_vector3_to_v3(const COLLADABU::Math::Vector3 &v3, float v[3]) +{ + v[0] = v3.x; + v[1] = v3.y; + v[2] = v3.z; +} diff --git a/source/blender/io/collada/TransformReader.h b/source/blender/io/collada/TransformReader.h new file mode 100644 index 00000000000..2cf3ee795ae --- /dev/null +++ b/source/blender/io/collada/TransformReader.h @@ -0,0 +1,72 @@ +/* + * 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 collada + */ + +#ifndef __TRANSFORMREADER_H__ +#define __TRANSFORMREADER_H__ + +#include "COLLADAFWNode.h" +#include "COLLADAFWTransformation.h" +#include "COLLADAFWTranslate.h" +#include "COLLADAFWRotate.h" +#include "COLLADAFWScale.h" +#include "COLLADAFWMatrix.h" +#include "COLLADAFWUniqueId.h" +#include "Math/COLLADABUMathVector3.h" + +#include "DNA_object_types.h" +#include "BLI_math.h" + +#include "collada_internal.h" + +// struct Object; + +class TransformReader { + protected: + UnitConverter *unit_converter; + + public: + struct Animation { + Object *ob; + COLLADAFW::Node *node; + COLLADAFW::Transformation *tm; // which transform is animated by an AnimationList->id + }; + + TransformReader(UnitConverter *conv); + + void get_node_mat(float mat[4][4], + COLLADAFW::Node *node, + std::map<COLLADAFW::UniqueId, Animation> *animation_map, + Object *ob); + void get_node_mat(float mat[4][4], + COLLADAFW::Node *node, + std::map<COLLADAFW::UniqueId, Animation> *animation_map, + Object *ob, + float parent_mat[4][4]); + + void dae_rotate_to_mat4(COLLADAFW::Transformation *tm, float m[4][4]); + void dae_translate_to_mat4(COLLADAFW::Transformation *tm, float m[4][4]); + void dae_scale_to_mat4(COLLADAFW::Transformation *tm, float m[4][4]); + void dae_matrix_to_mat4(COLLADAFW::Transformation *tm, float m[4][4]); + void dae_translate_to_v3(COLLADAFW::Transformation *tm, float v[3]); + void dae_scale_to_v3(COLLADAFW::Transformation *tm, float v[3]); + void dae_vector3_to_v3(const COLLADABU::Math::Vector3 &v3, float v[3]); +}; + +#endif diff --git a/source/blender/io/collada/TransformWriter.cpp b/source/blender/io/collada/TransformWriter.cpp new file mode 100644 index 00000000000..0a66db72cb9 --- /dev/null +++ b/source/blender/io/collada/TransformWriter.cpp @@ -0,0 +1,141 @@ +/* + * 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 collada + */ + +#include "BLI_math.h" +#include "BLI_sys_types.h" + +#include "BKE_object.h" + +#include "TransformWriter.h" + +void TransformWriter::add_joint_transform(COLLADASW::Node &node, + float mat[4][4], + float parent_mat[4][4], + BCExportSettings &export_settings, + bool has_restmat) +{ + float local[4][4]; + + if (parent_mat) { + float invpar[4][4]; + invert_m4_m4(invpar, parent_mat); + mul_m4_m4m4(local, invpar, mat); + } + else { + copy_m4_m4(local, mat); + } + + if (!has_restmat && export_settings.get_apply_global_orientation()) { + bc_apply_global_transform(local, export_settings.get_global_transform()); + } + + double dmat[4][4]; + UnitConverter *converter = new UnitConverter(); + converter->mat4_to_dae_double(dmat, local); + delete converter; + + if (export_settings.get_object_transformation_type() == BC_TRANSFORMATION_TYPE_MATRIX) { + node.addMatrix("transform", dmat); + } + else { + float loc[3], rot[3], scale[3]; + bc_decompose(local, loc, rot, NULL, scale); + add_transform(node, loc, rot, scale); + } +} + +void TransformWriter::add_node_transform_ob(COLLADASW::Node &node, + Object *ob, + BCExportSettings &export_settings) +{ + bool limit_precision = export_settings.get_limit_precision(); + + /* Export the local Matrix (relative to the object parent, + * be it an object, bone or vertex(-tices)). */ + Matrix f_obmat; + BKE_object_matrix_local_get(ob, f_obmat); + + if (export_settings.get_apply_global_orientation()) { + bc_apply_global_transform(f_obmat, export_settings.get_global_transform()); + } + else { + bc_add_global_transform(f_obmat, export_settings.get_global_transform()); + } + + switch (export_settings.get_object_transformation_type()) { + case BC_TRANSFORMATION_TYPE_MATRIX: { + UnitConverter converter; + double d_obmat[4][4]; + converter.mat4_to_dae_double(d_obmat, f_obmat); + + if (limit_precision) { + BCMatrix::sanitize(d_obmat, LIMITTED_PRECISION); + } + node.addMatrix("transform", d_obmat); + break; + } + case BC_TRANSFORMATION_TYPE_DECOMPOSED: { + float loc[3], rot[3], scale[3]; + bc_decompose(f_obmat, loc, rot, NULL, scale); + if (limit_precision) { + bc_sanitize_v3(loc, LIMITTED_PRECISION); + bc_sanitize_v3(rot, LIMITTED_PRECISION); + bc_sanitize_v3(scale, LIMITTED_PRECISION); + } + add_transform(node, loc, rot, scale); + break; + } + } +} + +void TransformWriter::add_node_transform_identity(COLLADASW::Node &node, + BCExportSettings &export_settings) +{ + BC_export_transformation_type transformation_type = + export_settings.get_object_transformation_type(); + switch (transformation_type) { + case BC_TRANSFORMATION_TYPE_MATRIX: { + BCMatrix mat; + DMatrix d_obmat; + mat.get_matrix(d_obmat); + node.addMatrix("transform", d_obmat); + break; + } + default: { + float loc[3] = {0.0f, 0.0f, 0.0f}; + float scale[3] = {1.0f, 1.0f, 1.0f}; + float rot[3] = {0.0f, 0.0f, 0.0f}; + add_transform(node, loc, rot, scale); + break; + } + } +} + +void TransformWriter::add_transform(COLLADASW::Node &node, + float loc[3], + float rot[3], + float scale[3]) +{ + node.addScale("scale", scale[0], scale[1], scale[2]); + node.addRotateZ("rotationZ", RAD2DEGF(rot[2])); + node.addRotateY("rotationY", RAD2DEGF(rot[1])); + node.addRotateX("rotationX", RAD2DEGF(rot[0])); + node.addTranslate("location", loc[0], loc[1], loc[2]); +} diff --git a/source/blender/io/collada/TransformWriter.h b/source/blender/io/collada/TransformWriter.h new file mode 100644 index 00000000000..d2e4b369cdc --- /dev/null +++ b/source/blender/io/collada/TransformWriter.h @@ -0,0 +1,48 @@ +/* + * 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 collada + */ + +#ifndef __TRANSFORMWRITER_H__ +#define __TRANSFORMWRITER_H__ + +#include "COLLADASWNode.h" + +#include "DNA_object_types.h" + +#include "collada_internal.h" +#include "collada_utils.h" +#include "collada.h" + +class TransformWriter { + protected: + void add_joint_transform(COLLADASW::Node &node, + float mat[4][4], + float parent_mat[4][4], + BCExportSettings &export_settings, + bool has_restmat); + + void add_node_transform_ob(COLLADASW::Node &node, Object *ob, BCExportSettings &export_settings); + + void add_node_transform_identity(COLLADASW::Node &node, BCExportSettings &export_settings); + + private: + void add_transform(COLLADASW::Node &node, float loc[3], float rot[3], float scale[3]); +}; + +#endif diff --git a/source/blender/io/collada/collada.cpp b/source/blender/io/collada/collada.cpp new file mode 100644 index 00000000000..ea5600aa850 --- /dev/null +++ b/source/blender/io/collada/collada.cpp @@ -0,0 +1,117 @@ +/* + * 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 collada + */ + +/* COLLADABU_ASSERT, may be able to remove later */ +#include "COLLADABUPlatform.h" + +#include "DocumentExporter.h" +#include "DocumentImporter.h" +#include "ExportSettings.h" +#include "ImportSettings.h" +#include "collada.h" + +extern "C" { +#include "BKE_scene.h" +#include "BKE_context.h" +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +/* make dummy file */ +#include "BLI_fileops.h" +#include "BLI_linklist.h" + +static void print_import_header(ImportSettings &import_settings) +{ + fprintf(stderr, "+-- Collada Import parameters------\n"); + fprintf(stderr, "| input file : %s\n", import_settings.filepath); + fprintf(stderr, "| use units : %s\n", (import_settings.import_units) ? "yes" : "no"); + fprintf(stderr, "| autoconnect : %s\n", (import_settings.auto_connect) ? "yes" : "no"); + fprintf(stderr, "+-- Armature Import parameters ----\n"); + fprintf(stderr, "| find bone chains: %s\n", (import_settings.find_chains) ? "yes" : "no"); + fprintf(stderr, "| min chain len : %d\n", import_settings.min_chain_length); + fprintf(stderr, "| fix orientation : %s\n", (import_settings.fix_orientation) ? "yes" : "no"); + fprintf(stderr, "| keep bind info : %s\n", (import_settings.keep_bind_info) ? "yes" : "no"); +} + +static void print_import_footer(int status) +{ + fprintf(stderr, "+----------------------------------\n"); + fprintf(stderr, "| Collada Import : %s\n", (status) ? "OK" : "FAIL"); + fprintf(stderr, "+----------------------------------\n"); +} + +int collada_import(bContext *C, ImportSettings *import_settings) +{ + print_import_header(*import_settings); + DocumentImporter imp(C, import_settings); + int status = imp.import() ? 1 : 0; + print_import_footer(status); + + return status; +} + +int collada_export(bContext *C, ExportSettings *export_settings) +{ + BlenderContext blender_context(C); + ViewLayer *view_layer = blender_context.get_view_layer(); + + int includeFilter = OB_REL_NONE; + if (export_settings->include_armatures) { + includeFilter |= OB_REL_MOD_ARMATURE; + } + if (export_settings->include_children) { + includeFilter |= OB_REL_CHILDREN_RECURSIVE; + } + + /* Fetch the complete set of exported objects + * ATTENTION: Invisible objects will not be exported + */ + eObjectSet objectSet = (export_settings->selected) ? OB_SET_SELECTED : OB_SET_ALL; + export_settings->export_set = BKE_object_relational_superset( + view_layer, objectSet, (eObRelationTypes)includeFilter); + + int export_count = BLI_linklist_count(export_settings->export_set); + + if (export_count == 0) { + if (export_settings->selected) { + fprintf(stderr, + "Collada: Found no objects to export.\nPlease ensure that all objects which shall " + "be exported are also visible in the 3D Viewport.\n"); + } + else { + fprintf(stderr, "Collada: Your scene seems to be empty. No Objects will be exported.\n"); + } + } + else { + if (export_settings->sort_by_name) { + bc_bubble_sort_by_Object_name(export_settings->export_set); + } + } + + DocumentExporter exporter(blender_context, export_settings); + int status = exporter.exportCurrentScene(); + + BLI_linklist_free(export_settings->export_set, NULL); + + return (status) ? -1 : export_count; +} + +/* end extern C */ +} diff --git a/source/blender/io/collada/collada.h b/source/blender/io/collada/collada.h new file mode 100644 index 00000000000..72753e170a3 --- /dev/null +++ b/source/blender/io/collada/collada.h @@ -0,0 +1,50 @@ +/* + * 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 collada + */ + +#ifndef __COLLADA_H__ +#define __COLLADA_H__ + +#include <stdlib.h> + +#include "ImportSettings.h" +#include "ExportSettings.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include "BLI_linklist.h" +#include "BLI_path_util.h" +#include "RNA_types.h" + +struct bContext; + +/* + * both return 1 on success, 0 on error + */ +int collada_import(struct bContext *C, ImportSettings *import_settings); + +int collada_export(struct bContext *C, ExportSettings *export_settings); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/blender/io/collada/collada_internal.cpp b/source/blender/io/collada/collada_internal.cpp new file mode 100644 index 00000000000..7e834045795 --- /dev/null +++ b/source/blender/io/collada/collada_internal.cpp @@ -0,0 +1,340 @@ +/* + * 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 collada + */ + +/* COLLADABU_ASSERT, may be able to remove later */ +#include "COLLADABUPlatform.h" +#include "collada_utils.h" + +#include "BLI_linklist.h" +#include "ED_armature.h" + +UnitConverter::UnitConverter() : unit(), up_axis(COLLADAFW::FileInfo::Z_UP) +{ + axis_angle_to_mat4_single(x_up_mat4, 'Y', -0.5 * M_PI); + axis_angle_to_mat4_single(y_up_mat4, 'X', 0.5 * M_PI); + + unit_m4(z_up_mat4); + unit_m4(scale_mat4); +} + +void UnitConverter::read_asset(const COLLADAFW::FileInfo *asset) +{ + unit = asset->getUnit(); + up_axis = asset->getUpAxisType(); +} + +UnitConverter::UnitSystem UnitConverter::isMetricSystem() +{ + switch (unit.getLinearUnitUnit()) { + case COLLADAFW::FileInfo::Unit::MILLIMETER: + case COLLADAFW::FileInfo::Unit::CENTIMETER: + case COLLADAFW::FileInfo::Unit::DECIMETER: + case COLLADAFW::FileInfo::Unit::METER: + case COLLADAFW::FileInfo::Unit::KILOMETER: + return UnitConverter::Metric; + case COLLADAFW::FileInfo::Unit::INCH: + case COLLADAFW::FileInfo::Unit::FOOT: + case COLLADAFW::FileInfo::Unit::YARD: + return UnitConverter::Imperial; + default: + return UnitConverter::None; + } +} + +float UnitConverter::getLinearMeter() +{ + return (float)unit.getLinearUnitMeter(); +} + +void UnitConverter::convertVector3(COLLADABU::Math::Vector3 &vec, float *v) +{ + v[0] = vec.x; + v[1] = vec.y; + v[2] = vec.z; +} + +// TODO need also for angle conversion, time conversion... + +void UnitConverter::dae_matrix_to_mat4_(float out[4][4], const COLLADABU::Math::Matrix4 &in) +{ + // in DAE, matrices use columns vectors, (see comments in COLLADABUMathMatrix4.h) + // so here, to make a blender matrix, we swap columns and rows + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + out[i][j] = in[j][i]; + } + } +} + +void UnitConverter::mat4_to_dae(float out[4][4], float in[4][4]) +{ + transpose_m4_m4(out, in); +} + +void UnitConverter::mat4_to_dae_double(double out[4][4], float in[4][4]) +{ + float mat[4][4]; + + mat4_to_dae(mat, in); + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + out[i][j] = mat[i][j]; + } + } +} + +float (&UnitConverter::get_rotation())[4][4] +{ + switch (up_axis) { + case COLLADAFW::FileInfo::X_UP: + return x_up_mat4; + break; + case COLLADAFW::FileInfo::Y_UP: + return y_up_mat4; + break; + default: + return z_up_mat4; + break; + } +} + +float (&UnitConverter::get_scale())[4][4] +{ + return scale_mat4; +} + +void UnitConverter::calculate_scale(Scene &sce) +{ + PointerRNA scene_ptr, unit_settings; + PropertyRNA *system_ptr, *scale_ptr; + RNA_id_pointer_create(&sce.id, &scene_ptr); + + unit_settings = RNA_pointer_get(&scene_ptr, "unit_settings"); + system_ptr = RNA_struct_find_property(&unit_settings, "system"); + scale_ptr = RNA_struct_find_property(&unit_settings, "scale_length"); + + int type = RNA_property_enum_get(&unit_settings, system_ptr); + + float bl_scale; + + switch (type) { + case USER_UNIT_NONE: + bl_scale = 1.0; // map 1 Blender unit to 1 Meter + break; + + case USER_UNIT_METRIC: + bl_scale = RNA_property_float_get(&unit_settings, scale_ptr); + break; + + default: + bl_scale = RNA_property_float_get(&unit_settings, scale_ptr); + // it looks like the conversion to Imperial is done implicitly. + // So nothing to do here. + break; + } + + float rescale[3]; + rescale[0] = rescale[1] = rescale[2] = getLinearMeter() / bl_scale; + + size_to_mat4(scale_mat4, rescale); +} + +/** + * Translation map. + * Used to translate every COLLADA id to a valid id, no matter what "wrong" letters may be + * included. Look at the IDREF XSD declaration for more. + * Follows strictly the COLLADA XSD declaration which explicitly allows non-english chars, + * like special chars (e.g. micro sign), umlauts and so on. + * The COLLADA spec also allows additional chars for member access ('.'), these + * must obviously be removed too, otherwise they would be heavily misinterpreted. + */ +const unsigned char translate_start_name_map[256] = { + + 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, + 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, + 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, + 95, 95, 95, 95, 95, 95, 95, 95, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, + 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 95, 95, 95, 95, + 95, 95, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, + 114, 115, 116, 117, 118, 119, 120, 121, 122, 95, 95, 95, 95, 95, + + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, + 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, + 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, + 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, + 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, + 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, + 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, +}; + +const unsigned char translate_name_map[256] = { + + 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, + 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, + 95, 95, 95, 95, 95, 95, 95, 45, 95, 95, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 57, 95, 95, 95, 95, 95, 95, 95, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, + 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 95, 95, 95, 95, + 95, 95, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, + 114, 115, 116, 117, 118, 119, 120, 121, 122, 95, 95, 95, 95, 95, + + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, + 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, + 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, + 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, + 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, + 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, + 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, +}; + +typedef std::map<std::string, std::vector<std::string>> map_string_list; +map_string_list global_id_map; + +void clear_global_id_map() +{ + global_id_map.clear(); +} + +/** Look at documentation of translate_map */ +std::string translate_id(const char *idString) +{ + std::string id = std::string(idString); + return translate_id(id); +} + +std::string translate_id(const std::string &id) +{ + if (id.size() == 0) { + return id; + } + + std::string id_translated = id; + id_translated[0] = translate_start_name_map[(unsigned int)id_translated[0]]; + for (unsigned int i = 1; i < id_translated.size(); i++) { + id_translated[i] = translate_name_map[(unsigned int)id_translated[i]]; + } + // It's so much workload now, the if () should speed up things. + if (id_translated != id) { + // Search duplicates + map_string_list::iterator iter = global_id_map.find(id_translated); + if (iter != global_id_map.end()) { + unsigned int i = 0; + bool found = false; + for (i = 0; i < iter->second.size(); i++) { + if (id == iter->second[i]) { + found = true; + break; + } + } + bool convert = false; + if (found) { + if (i > 0) { + convert = true; + } + } + else { + convert = true; + global_id_map[id_translated].push_back(id); + } + if (convert) { + std::stringstream out; + out << ++i; + id_translated += out.str(); + } + } + else { + global_id_map[id_translated].push_back(id); + } + } + return id_translated; +} + +std::string id_name(void *id) +{ + return ((ID *)id)->name + 2; +} + +std::string encode_xml(std::string xml) +{ + const std::map<char, std::string> escape{ + {'<', "<"}, {'>', ">"}, {'"', """}, {'\'', "'"}, {'&', "&"}}; + + std::map<char, std::string>::const_iterator it; + std::string encoded_xml = ""; + + for (unsigned int i = 0; i < xml.size(); i++) { + char c = xml.at(i); + it = escape.find(c); + + if (it == escape.end()) { + encoded_xml += c; + } + else { + encoded_xml += it->second; + } + } + return encoded_xml; +} + +std::string get_geometry_id(Object *ob) +{ + return translate_id(id_name(ob->data)) + "-mesh"; +} + +std::string get_geometry_id(Object *ob, bool use_instantiation) +{ + std::string geom_name = (use_instantiation) ? id_name(ob->data) : id_name(ob); + + return translate_id(geom_name) + "-mesh"; +} + +std::string get_light_id(Object *ob) +{ + return translate_id(id_name(ob)) + "-light"; +} + +std::string get_joint_sid(Bone *bone) +{ + return translate_id(bone->name); +} +static std::string get_joint_sid(EditBone *bone) +{ + return translate_id(bone->name); +} + +std::string get_camera_id(Object *ob) +{ + return translate_id(id_name(ob)) + "-camera"; +} + +std::string get_effect_id(Material *mat) +{ + return translate_id(id_name(mat)) + "-effect"; +} + +std::string get_material_id(Material *mat) +{ + return translate_id(id_name(mat)) + "-material"; +} + +std::string get_morph_id(Object *ob) +{ + return translate_id(id_name(ob)) + "-morph"; +} diff --git a/source/blender/io/collada/collada_internal.h b/source/blender/io/collada/collada_internal.h new file mode 100644 index 00000000000..297ea9c0bbb --- /dev/null +++ b/source/blender/io/collada/collada_internal.h @@ -0,0 +1,98 @@ +/* + * 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 collada + */ + +#ifndef __COLLADA_INTERNAL_H__ +#define __COLLADA_INTERNAL_H__ + +#include <string> +#include <vector> +#include <map> + +#include "COLLADAFWFileInfo.h" +#include "Math/COLLADABUMathMatrix4.h" + +#include "DNA_armature_types.h" +#include "DNA_material_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "BLI_math.h" +#include "BLI_linklist.h" + +class UnitConverter { + private: + COLLADAFW::FileInfo::Unit unit; + COLLADAFW::FileInfo::UpAxisType up_axis; + + float x_up_mat4[4][4]; + float y_up_mat4[4][4]; + float z_up_mat4[4][4]; + float scale_mat4[4][4]; + + public: + enum UnitSystem { + None, + Metric, + Imperial, + }; + + // Initialize with Z_UP, since Blender uses right-handed, z-up + UnitConverter(); + + void read_asset(const COLLADAFW::FileInfo *asset); + + void convertVector3(COLLADABU::Math::Vector3 &vec, float *v); + + UnitConverter::UnitSystem isMetricSystem(void); + + float getLinearMeter(void); + + // TODO need also for angle conversion, time conversion... + + static void dae_matrix_to_mat4_(float out[4][4], const COLLADABU::Math::Matrix4 &in); + static void mat4_to_dae(float out[4][4], float in[4][4]); + static void mat4_to_dae_double(double out[4][4], float in[4][4]); + + float (&get_rotation())[4][4]; + float (&get_scale())[4][4]; + void calculate_scale(Scene &sce); +}; + +extern void clear_global_id_map(); +/** Look at documentation of translate_map */ +extern std::string translate_id(const std::string &id); +extern std::string translate_id(const char *idString); + +extern std::string id_name(void *id); +extern std::string encode_xml(std::string xml); + +extern std::string get_geometry_id(Object *ob); +extern std::string get_geometry_id(Object *ob, bool use_instantiation); + +extern std::string get_light_id(Object *ob); + +extern std::string get_joint_sid(Bone *bone); + +extern std::string get_camera_id(Object *ob); +extern std::string get_morph_id(Object *ob); + +extern std::string get_effect_id(Material *mat); +extern std::string get_material_id(Material *mat); + +#endif /* __COLLADA_INTERNAL_H__ */ diff --git a/source/blender/io/collada/collada_utils.cpp b/source/blender/io/collada/collada_utils.cpp new file mode 100644 index 00000000000..26b392af0a1 --- /dev/null +++ b/source/blender/io/collada/collada_utils.cpp @@ -0,0 +1,1458 @@ +/* + * 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 collada + */ + +/* COLLADABU_ASSERT, may be able to remove later */ +#include "COLLADABUPlatform.h" + +#include "COLLADAFWGeometry.h" +#include "COLLADAFWMeshPrimitive.h" +#include "COLLADAFWMeshVertexData.h" + +#include <set> +#include <string> + +#include "MEM_guardedalloc.h" + +extern "C" { +#include "DNA_modifier_types.h" +#include "DNA_customdata_types.h" +#include "DNA_key_types.h" +#include "DNA_object_types.h" +#include "DNA_constraint_types.h" +#include "DNA_mesh_types.h" +#include "DNA_scene_types.h" +#include "DNA_armature_types.h" + +#include "BLI_math.h" +#include "BLI_linklist.h" +#include "BLI_listbase.h" + +#include "BKE_action.h" +#include "BKE_context.h" +#include "BKE_customdata.h" +#include "BKE_constraint.h" +#include "BKE_key.h" +#include "BKE_material.h" +#include "BKE_node.h" +#include "BKE_object.h" +#include "BKE_global.h" +#include "BKE_layer.h" +#include "BKE_lib_id.h" +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_object.h" +#include "BKE_scene.h" + +#include "ED_armature.h" +#include "ED_screen.h" +#include "ED_node.h" +#include "ED_object.h" + +#include "WM_api.h" /* XXX hrm, see if we can do without this */ +#include "WM_types.h" + +#include "bmesh.h" +#include "bmesh_tools.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" +#if 0 +# include "NOD_common.h" +#endif +} + +#include "collada_utils.h" +#include "ExportSettings.h" +#include "BlenderContext.h" + +float bc_get_float_value(const COLLADAFW::FloatOrDoubleArray &array, unsigned int index) +{ + if (index >= array.getValuesCount()) { + return 0.0f; + } + + if (array.getType() == COLLADAFW::MeshVertexData::DATA_TYPE_FLOAT) { + return array.getFloatValues()->getData()[index]; + } + else { + return array.getDoubleValues()->getData()[index]; + } +} + +/* copied from /editors/object/object_relations.c */ +int bc_test_parent_loop(Object *par, Object *ob) +{ + /* test if 'ob' is a parent somewhere in par's parents */ + + if (par == NULL) { + return 0; + } + if (ob == par) { + return 1; + } + + return bc_test_parent_loop(par->parent, ob); +} + +bool bc_validateConstraints(bConstraint *con) +{ + const bConstraintTypeInfo *cti = BKE_constraint_typeinfo_get(con); + + /* these we can skip completely (invalid constraints...) */ + if (cti == NULL) { + return false; + } + if (con->flag & (CONSTRAINT_DISABLE | CONSTRAINT_OFF)) { + return false; + } + + /* these constraints can't be evaluated anyway */ + if (cti->evaluate_constraint == NULL) { + return false; + } + + /* influence == 0 should be ignored */ + if (con->enforce == 0.0f) { + return false; + } + + /* validation passed */ + return true; +} + +bool bc_set_parent(Object *ob, Object *par, bContext *C, bool is_parent_space) +{ + Scene *scene = CTX_data_scene(C); + int partype = PAR_OBJECT; + const bool xmirror = false; + const bool keep_transform = false; + + if (par && is_parent_space) { + mul_m4_m4m4(ob->obmat, par->obmat, ob->obmat); + } + + bool ok = ED_object_parent_set(NULL, C, scene, ob, par, partype, xmirror, keep_transform, NULL); + return ok; +} + +std::vector<bAction *> bc_getSceneActions(const bContext *C, Object *ob, bool all_actions) +{ + std::vector<bAction *> actions; + if (all_actions) { + Main *bmain = CTX_data_main(C); + ID *id; + + for (id = (ID *)bmain->actions.first; id; id = (ID *)(id->next)) { + bAction *act = (bAction *)id; + /* XXX This currently creates too many actions. + * TODO Need to check if the action is compatible to the given object. */ + actions.push_back(act); + } + } + else { + bAction *action = bc_getSceneObjectAction(ob); + actions.push_back(action); + } + + return actions; +} + +std::string bc_get_action_id(std::string action_name, + std::string ob_name, + std::string channel_type, + std::string axis_name, + std::string axis_separator) +{ + std::string result = action_name + "_" + channel_type; + if (ob_name.length() > 0) { + result = ob_name + "_" + result; + } + if (axis_name.length() > 0) { + result += axis_separator + axis_name; + } + return translate_id(result); +} + +void bc_update_scene(BlenderContext &blender_context, float ctime) +{ + Main *bmain = blender_context.get_main(); + Scene *scene = blender_context.get_scene(); + Depsgraph *depsgraph = blender_context.get_depsgraph(); + + /* See remark in physics_fluid.c lines 395...) */ + // BKE_scene_update_for_newframe(ev_context, bmain, scene, scene->lay); + BKE_scene_frame_set(scene, ctime); + ED_update_for_newframe(bmain, depsgraph); +} + +Object *bc_add_object(Main *bmain, Scene *scene, ViewLayer *view_layer, int type, const char *name) +{ + Object *ob = BKE_object_add_only_object(bmain, type, name); + + ob->data = BKE_object_obdata_add_from_type(bmain, type, name); + DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION); + + LayerCollection *layer_collection = BKE_layer_collection_get_active(view_layer); + BKE_collection_object_add(bmain, layer_collection->collection, ob); + + Base *base = BKE_view_layer_base_find(view_layer, ob); + /* TODO: is setting active needed? */ + BKE_view_layer_base_select_and_set_active(view_layer, base); + + return ob; +} + +Mesh *bc_get_mesh_copy(BlenderContext &blender_context, + Object *ob, + BC_export_mesh_type export_mesh_type, + bool apply_modifiers, + bool triangulate) +{ + CustomData_MeshMasks mask = CD_MASK_MESH; + Mesh *tmpmesh = NULL; + if (apply_modifiers) { +#if 0 /* Not supported by new system currently... */ + switch (export_mesh_type) { + case BC_MESH_TYPE_VIEW: { + dm = mesh_create_derived_view(depsgraph, scene, ob, &mask); + break; + } + case BC_MESH_TYPE_RENDER: { + dm = mesh_create_derived_render(depsgraph, scene, ob, &mask); + break; + } + } +#else + Depsgraph *depsgraph = blender_context.get_depsgraph(); + Scene *scene_eval = blender_context.get_evaluated_scene(); + Object *ob_eval = blender_context.get_evaluated_object(ob); + tmpmesh = mesh_get_eval_final(depsgraph, scene_eval, ob_eval, &mask); +#endif + } + else { + tmpmesh = (Mesh *)ob->data; + } + + BKE_id_copy_ex(NULL, &tmpmesh->id, (ID **)&tmpmesh, LIB_ID_COPY_LOCALIZE); + + if (triangulate) { + bc_triangulate_mesh(tmpmesh); + } + BKE_mesh_tessface_ensure(tmpmesh); + return tmpmesh; +} + +Object *bc_get_assigned_armature(Object *ob) +{ + Object *ob_arm = NULL; + + if (ob->parent && ob->partype == PARSKEL && ob->parent->type == OB_ARMATURE) { + ob_arm = ob->parent; + } + else { + ModifierData *mod; + for (mod = (ModifierData *)ob->modifiers.first; mod; mod = mod->next) { + if (mod->type == eModifierType_Armature) { + ob_arm = ((ArmatureModifierData *)mod)->object; + } + } + } + + return ob_arm; +} + +bool bc_has_object_type(LinkNode *export_set, short obtype) +{ + LinkNode *node; + + for (node = export_set; node; node = node->next) { + Object *ob = (Object *)node->link; + /* XXX - why is this checking for ob->data? - we could be looking for empties */ + if (ob->type == obtype && ob->data) { + return true; + } + } + return false; +} + +/* Use bubble sort algorithm for sorting the export set */ +void bc_bubble_sort_by_Object_name(LinkNode *export_set) +{ + bool sorted = false; + LinkNode *node; + for (node = export_set; node->next && !sorted; node = node->next) { + + sorted = true; + + LinkNode *current; + for (current = export_set; current->next; current = current->next) { + Object *a = (Object *)current->link; + Object *b = (Object *)current->next->link; + + if (strcmp(a->id.name, b->id.name) > 0) { + current->link = b; + current->next->link = a; + sorted = false; + } + } + } +} + +/* Check if a bone is the top most exportable bone in the bone hierarchy. + * When deform_bones_only == false, then only bones with NO parent + * can be root bones. Otherwise the top most deform bones in the hierarchy + * are root bones. + */ +bool bc_is_root_bone(Bone *aBone, bool deform_bones_only) +{ + if (deform_bones_only) { + Bone *root = NULL; + Bone *bone = aBone; + while (bone) { + if (!(bone->flag & BONE_NO_DEFORM)) { + root = bone; + } + bone = bone->parent; + } + return (aBone == root); + } + else { + return !(aBone->parent); + } +} + +int bc_get_active_UVLayer(Object *ob) +{ + Mesh *me = (Mesh *)ob->data; + return CustomData_get_active_layer_index(&me->ldata, CD_MLOOPUV); +} + +std::string bc_url_encode(std::string data) +{ + /* XXX We probably do not need to do a full encoding. + * But in case that is necessary,then it can be added here. + */ + return bc_replace_string(data, "#", "%23"); +} + +std::string bc_replace_string(std::string data, + const std::string &pattern, + const std::string &replacement) +{ + size_t pos = 0; + while ((pos = data.find(pattern, pos)) != std::string::npos) { + data.replace(pos, pattern.length(), replacement); + pos += replacement.length(); + } + return data; +} + +/** + * Calculate a rescale factor such that the imported scene's scale + * is preserved. I.e. 1 meter in the import will also be + * 1 meter in the current scene. + */ + +void bc_match_scale(Object *ob, UnitConverter &bc_unit, bool scale_to_scene) +{ + if (scale_to_scene) { + mul_m4_m4m4(ob->obmat, bc_unit.get_scale(), ob->obmat); + } + mul_m4_m4m4(ob->obmat, bc_unit.get_rotation(), ob->obmat); + BKE_object_apply_mat4(ob, ob->obmat, 0, 0); +} + +void bc_match_scale(std::vector<Object *> *objects_done, + UnitConverter &bc_unit, + bool scale_to_scene) +{ + for (std::vector<Object *>::iterator it = objects_done->begin(); it != objects_done->end(); + ++it) { + Object *ob = *it; + if (ob->parent == NULL) { + bc_match_scale(*it, bc_unit, scale_to_scene); + } + } +} + +/* + * Convenience function to get only the needed components of a matrix + */ +void bc_decompose(float mat[4][4], float *loc, float eul[3], float quat[4], float *size) +{ + if (size) { + mat4_to_size(size, mat); + } + + if (eul) { + mat4_to_eul(eul, mat); + } + + if (quat) { + mat4_to_quat(quat, mat); + } + + if (loc) { + copy_v3_v3(loc, mat[3]); + } +} + +/* + * Create rotation_quaternion from a delta rotation and a reference quat + * + * Input: + * mat_from: The rotation matrix before rotation + * mat_to : The rotation matrix after rotation + * qref : the quat corresponding to mat_from + * + * Output: + * rot : the calculated result (quaternion) + */ +void bc_rotate_from_reference_quat(float quat_to[4], float quat_from[4], float mat_to[4][4]) +{ + float qd[4]; + float matd[4][4]; + float mati[4][4]; + float mat_from[4][4]; + quat_to_mat4(mat_from, quat_from); + + /* Calculate the difference matrix matd between mat_from and mat_to */ + invert_m4_m4(mati, mat_from); + mul_m4_m4m4(matd, mati, mat_to); + + mat4_to_quat(qd, matd); + + mul_qt_qtqt(quat_to, qd, quat_from); /* rot is the final rotation corresponding to mat_to */ +} + +void bc_triangulate_mesh(Mesh *me) +{ + bool use_beauty = false; + bool tag_only = false; + + /* XXX: The triangulation method selection could be offered in the UI. */ + int quad_method = MOD_TRIANGULATE_QUAD_SHORTEDGE; + + const struct BMeshCreateParams bm_create_params = {0}; + BMesh *bm = BM_mesh_create(&bm_mesh_allocsize_default, &bm_create_params); + BMeshFromMeshParams bm_from_me_params = {0}; + bm_from_me_params.calc_face_normal = true; + BM_mesh_bm_from_me(bm, me, &bm_from_me_params); + BM_mesh_triangulate(bm, quad_method, use_beauty, 4, tag_only, NULL, NULL, NULL); + + BMeshToMeshParams bm_to_me_params = {0}; + bm_to_me_params.calc_object_remap = false; + BM_mesh_bm_to_me(NULL, bm, me, &bm_to_me_params); + BM_mesh_free(bm); +} + +/* + * A bone is a leaf when it has no children or all children are not connected. + */ +bool bc_is_leaf_bone(Bone *bone) +{ + for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) { + if (child->flag & BONE_CONNECTED) { + return false; + } + } + return true; +} + +EditBone *bc_get_edit_bone(bArmature *armature, char *name) +{ + EditBone *eBone; + + for (eBone = (EditBone *)armature->edbo->first; eBone; eBone = eBone->next) { + if (STREQ(name, eBone->name)) { + return eBone; + } + } + + return NULL; +} +int bc_set_layer(int bitfield, int layer) +{ + return bc_set_layer(bitfield, layer, true); /* enable */ +} + +int bc_set_layer(int bitfield, int layer, bool enable) +{ + int bit = 1u << layer; + + if (enable) { + bitfield |= bit; + } + else { + bitfield &= ~bit; + } + + return bitfield; +} + +/** + * This method creates a new extension map when needed. + * \note The ~BoneExtensionManager destructor takes care + * to delete the created maps when the manager is removed. + */ +BoneExtensionMap &BoneExtensionManager::getExtensionMap(bArmature *armature) +{ + std::string key = armature->id.name; + BoneExtensionMap *result = extended_bone_maps[key]; + if (result == NULL) { + result = new BoneExtensionMap(); + extended_bone_maps[key] = result; + } + return *result; +} + +BoneExtensionManager::~BoneExtensionManager() +{ + std::map<std::string, BoneExtensionMap *>::iterator map_it; + for (map_it = extended_bone_maps.begin(); map_it != extended_bone_maps.end(); ++map_it) { + BoneExtensionMap *extended_bones = map_it->second; + for (BoneExtensionMap::iterator ext_it = extended_bones->begin(); + ext_it != extended_bones->end(); + ++ext_it) { + if (ext_it->second != NULL) { + delete ext_it->second; + } + } + extended_bones->clear(); + delete extended_bones; + } +} + +/** + * BoneExtended is a helper class needed for the Bone chain finder + * See ArmatureImporter::fix_leaf_bones() + * and ArmatureImporter::connect_bone_chains() + */ + +BoneExtended::BoneExtended(EditBone *aBone) +{ + this->set_name(aBone->name); + this->chain_length = 0; + this->is_leaf = false; + this->tail[0] = 0.0f; + this->tail[1] = 0.5f; + this->tail[2] = 0.0f; + this->use_connect = -1; + this->roll = 0; + this->bone_layers = 0; + + this->has_custom_tail = false; + this->has_custom_roll = false; +} + +char *BoneExtended::get_name() +{ + return name; +} + +void BoneExtended::set_name(char *aName) +{ + BLI_strncpy(name, aName, MAXBONENAME); +} + +int BoneExtended::get_chain_length() +{ + return chain_length; +} + +void BoneExtended::set_chain_length(const int aLength) +{ + chain_length = aLength; +} + +void BoneExtended::set_leaf_bone(bool state) +{ + is_leaf = state; +} + +bool BoneExtended::is_leaf_bone() +{ + return is_leaf; +} + +void BoneExtended::set_roll(float roll) +{ + this->roll = roll; + this->has_custom_roll = true; +} + +bool BoneExtended::has_roll() +{ + return this->has_custom_roll; +} + +float BoneExtended::get_roll() +{ + return this->roll; +} + +void BoneExtended::set_tail(float vec[]) +{ + this->tail[0] = vec[0]; + this->tail[1] = vec[1]; + this->tail[2] = vec[2]; + this->has_custom_tail = true; +} + +bool BoneExtended::has_tail() +{ + return this->has_custom_tail; +} + +float *BoneExtended::get_tail() +{ + return this->tail; +} + +inline bool isInteger(const std::string &s) +{ + if (s.empty() || ((!isdigit(s[0])) && (s[0] != '-') && (s[0] != '+'))) { + return false; + } + + char *p; + strtol(s.c_str(), &p, 10); + + return (*p == 0); +} + +void BoneExtended::set_bone_layers(std::string layerString, std::vector<std::string> &layer_labels) +{ + std::stringstream ss(layerString); + std::string layer; + int pos; + + while (ss >> layer) { + + /* Blender uses numbers to specify layers*/ + if (isInteger(layer)) { + pos = atoi(layer.c_str()); + if (pos >= 0 && pos < 32) { + this->bone_layers = bc_set_layer(this->bone_layers, pos); + continue; + } + } + + /* layer uses labels (not supported by blender). Map to layer numbers:*/ + pos = find(layer_labels.begin(), layer_labels.end(), layer) - layer_labels.begin(); + if (pos >= layer_labels.size()) { + layer_labels.push_back(layer); /* remember layer number for future usage*/ + } + + if (pos > 31) { + fprintf(stderr, + "Too many layers in Import. Layer %s mapped to Blender layer 31\n", + layer.c_str()); + pos = 31; + } + + /* If numeric layers and labeled layers are used in parallel (unlikely), + * we get a potential mixup. Just leave as is for now. + */ + this->bone_layers = bc_set_layer(this->bone_layers, pos); + } +} + +std::string BoneExtended::get_bone_layers(int bitfield) +{ + std::string result = ""; + std::string sep = ""; + int bit = 1u; + + std::ostringstream ss; + for (int i = 0; i < 32; i++) { + if (bit & bitfield) { + ss << sep << i; + sep = " "; + } + bit = bit << 1; + } + return ss.str(); +} + +int BoneExtended::get_bone_layers() +{ + /* ensure that the bone is in at least one bone layer! */ + return (bone_layers == 0) ? 1 : bone_layers; +} + +void BoneExtended::set_use_connect(int use_connect) +{ + this->use_connect = use_connect; +} + +int BoneExtended::get_use_connect() +{ + return this->use_connect; +} + +/** + * Stores a 4*4 matrix as a custom bone property array of size 16 + */ +void bc_set_IDPropertyMatrix(EditBone *ebone, const char *key, float mat[4][4]) +{ + IDProperty *idgroup = (IDProperty *)ebone->prop; + if (idgroup == NULL) { + IDPropertyTemplate val = {0}; + idgroup = IDP_New(IDP_GROUP, &val, "RNA_EditBone ID properties"); + ebone->prop = idgroup; + } + + IDPropertyTemplate val = {0}; + val.array.len = 16; + val.array.type = IDP_FLOAT; + + IDProperty *data = IDP_New(IDP_ARRAY, &val, key); + float *array = (float *)IDP_Array(data); + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + array[4 * i + j] = mat[i][j]; + } + } + + IDP_AddToGroup(idgroup, data); +} + +#if 0 +/** + * Stores a Float value as a custom bone property + * + * Note: This function is currently not needed. Keep for future usage + */ +static void bc_set_IDProperty(EditBone *ebone, const char *key, float value) +{ + if (ebone->prop == NULL) { + IDPropertyTemplate val = {0}; + ebone->prop = IDP_New(IDP_GROUP, &val, "RNA_EditBone ID properties"); + } + + IDProperty *pgroup = (IDProperty *)ebone->prop; + IDPropertyTemplate val = {0}; + IDProperty *prop = IDP_New(IDP_FLOAT, &val, key); + IDP_Float(prop) = value; + IDP_AddToGroup(pgroup, prop); +} +#endif + +/** + * Get a custom property when it exists. + * This function is also used to check if a property exists. + */ +IDProperty *bc_get_IDProperty(Bone *bone, std::string key) +{ + return (bone->prop == NULL) ? NULL : IDP_GetPropertyFromGroup(bone->prop, key.c_str()); +} + +/** + * Read a custom bone property and convert to float + * Return def if the property does not exist. + */ +float bc_get_property(Bone *bone, std::string key, float def) +{ + float result = def; + IDProperty *property = bc_get_IDProperty(bone, key); + if (property) { + switch (property->type) { + case IDP_INT: + result = (float)(IDP_Int(property)); + break; + case IDP_FLOAT: + result = (float)(IDP_Float(property)); + break; + case IDP_DOUBLE: + result = (float)(IDP_Double(property)); + break; + default: + result = def; + } + } + return result; +} + +/** + * Read a custom bone property and convert to matrix + * Return true if conversion was successful + * + * Return false if: + * - the property does not exist + * - is not an array of size 16 + */ +bool bc_get_property_matrix(Bone *bone, std::string key, float mat[4][4]) +{ + IDProperty *property = bc_get_IDProperty(bone, key); + if (property && property->type == IDP_ARRAY && property->len == 16) { + float *array = (float *)IDP_Array(property); + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + mat[i][j] = array[4 * i + j]; + } + } + return true; + } + return false; +} + +/** + * get a vector that is stored in 3 custom properties (used in Blender <= 2.78) + */ +void bc_get_property_vector(Bone *bone, std::string key, float val[3], const float def[3]) +{ + val[0] = bc_get_property(bone, key + "_x", def[0]); + val[1] = bc_get_property(bone, key + "_y", def[1]); + val[2] = bc_get_property(bone, key + "_z", def[2]); +} + +/** + * Check if vector exist stored in 3 custom properties (used in Blender <= 2.78) + */ +static bool has_custom_props(Bone *bone, bool enabled, std::string key) +{ + if (!enabled) { + return false; + } + + return (bc_get_IDProperty(bone, key + "_x") || bc_get_IDProperty(bone, key + "_y") || + bc_get_IDProperty(bone, key + "_z")); +} + +void bc_enable_fcurves(bAction *act, char *bone_name) +{ + FCurve *fcu; + char prefix[200]; + + if (bone_name) { + BLI_snprintf(prefix, sizeof(prefix), "pose.bones[\"%s\"]", bone_name); + } + + for (fcu = (FCurve *)act->curves.first; fcu; fcu = fcu->next) { + if (bone_name) { + if (STREQLEN(fcu->rna_path, prefix, strlen(prefix))) { + fcu->flag &= ~FCURVE_DISABLED; + } + else { + fcu->flag |= FCURVE_DISABLED; + } + } + else { + fcu->flag &= ~FCURVE_DISABLED; + } + } +} + +bool bc_bone_matrix_local_get(Object *ob, Bone *bone, Matrix &mat, bool for_opensim) +{ + + /* Ok, lets be super cautious and check if the bone exists */ + bPose *pose = ob->pose; + bPoseChannel *pchan = BKE_pose_channel_find_name(pose, bone->name); + if (!pchan) { + return false; + } + + bAction *action = bc_getSceneObjectAction(ob); + bPoseChannel *parchan = pchan->parent; + + bc_enable_fcurves(action, bone->name); + float ipar[4][4]; + + if (bone->parent) { + invert_m4_m4(ipar, parchan->pose_mat); + mul_m4_m4m4(mat, ipar, pchan->pose_mat); + } + else { + copy_m4_m4(mat, pchan->pose_mat); + } + + /* OPEN_SIM_COMPATIBILITY + * AFAIK animation to second life is via BVH, but no + * reason to not have the collada-animation be correct */ + if (for_opensim) { + float temp[4][4]; + copy_m4_m4(temp, bone->arm_mat); + temp[3][0] = temp[3][1] = temp[3][2] = 0.0f; + invert_m4(temp); + + mul_m4_m4m4(mat, mat, temp); + + if (bone->parent) { + copy_m4_m4(temp, bone->parent->arm_mat); + temp[3][0] = temp[3][1] = temp[3][2] = 0.0f; + + mul_m4_m4m4(mat, temp, mat); + } + } + bc_enable_fcurves(action, NULL); + return true; +} + +bool bc_is_animated(BCMatrixSampleMap &values) +{ + static float MIN_DISTANCE = 0.00001; + + if (values.size() < 2) { + return false; /* need at least 2 entries to be not flat */ + } + + BCMatrixSampleMap::iterator it; + const BCMatrix *refmat = NULL; + for (it = values.begin(); it != values.end(); ++it) { + const BCMatrix *matrix = it->second; + + if (refmat == NULL) { + refmat = matrix; + continue; + } + + if (!matrix->in_range(*refmat, MIN_DISTANCE)) { + return true; + } + } + return false; +} + +bool bc_has_animations(Object *ob) +{ + /* Check for object, light and camera transform animations */ + if ((bc_getSceneObjectAction(ob) && bc_getSceneObjectAction(ob)->curves.first) || + (bc_getSceneLightAction(ob) && bc_getSceneLightAction(ob)->curves.first) || + (bc_getSceneCameraAction(ob) && bc_getSceneCameraAction(ob)->curves.first)) { + return true; + } + + /* Check Material Effect parameter animations. */ + for (int a = 0; a < ob->totcol; a++) { + Material *ma = BKE_object_material_get(ob, a + 1); + if (!ma) { + continue; + } + if (ma->adt && ma->adt->action && ma->adt->action->curves.first) { + return true; + } + } + + Key *key = BKE_key_from_object(ob); + if ((key && key->adt && key->adt->action) && key->adt->action->curves.first) { + return true; + } + + return false; +} + +bool bc_has_animations(Scene *sce, LinkNode *export_set) +{ + LinkNode *node; + if (export_set) { + for (node = export_set; node; node = node->next) { + Object *ob = (Object *)node->link; + + if (bc_has_animations(ob)) { + return true; + } + } + } + return false; +} + +void bc_add_global_transform(Matrix &to_mat, + const Matrix &from_mat, + const BCMatrix &global_transform, + const bool invert) +{ + copy_m4_m4(to_mat, from_mat); + bc_add_global_transform(to_mat, global_transform, invert); +} + +void bc_add_global_transform(Vector &to_vec, + const Vector &from_vec, + const BCMatrix &global_transform, + const bool invert) +{ + copy_v3_v3(to_vec, from_vec); + bc_add_global_transform(to_vec, global_transform, invert); +} + +void bc_add_global_transform(Matrix &to_mat, const BCMatrix &global_transform, const bool invert) +{ + BCMatrix mat(to_mat); + mat.add_transform(global_transform, invert); + mat.get_matrix(to_mat); +} + +void bc_add_global_transform(Vector &to_vec, const BCMatrix &global_transform, const bool invert) +{ + Matrix mat; + Vector from_vec; + copy_v3_v3(from_vec, to_vec); + global_transform.get_matrix(mat, false, 6, invert); + mul_v3_m4v3(to_vec, mat, from_vec); +} + +void bc_apply_global_transform(Matrix &to_mat, const BCMatrix &global_transform, const bool invert) +{ + BCMatrix mat(to_mat); + mat.apply_transform(global_transform, invert); + mat.get_matrix(to_mat); +} + +void bc_apply_global_transform(Vector &to_vec, const BCMatrix &global_transform, const bool invert) +{ + Matrix transform; + global_transform.get_matrix(transform); + mul_v3_m4v3(to_vec, transform, to_vec); +} + +/** + * Check if custom information about bind matrix exists and modify the from_mat + * accordingly. + * + * Note: This is old style for Blender <= 2.78 only kept for compatibility + */ +void bc_create_restpose_mat(BCExportSettings &export_settings, + Bone *bone, + float to_mat[4][4], + float from_mat[4][4], + bool use_local_space) +{ + float loc[3]; + float rot[3]; + float scale[3]; + static const float V0[3] = {0, 0, 0}; + + if (!has_custom_props(bone, export_settings.get_keep_bind_info(), "restpose_loc") && + !has_custom_props(bone, export_settings.get_keep_bind_info(), "restpose_rot") && + !has_custom_props(bone, export_settings.get_keep_bind_info(), "restpose_scale")) { + /* No need */ + copy_m4_m4(to_mat, from_mat); + return; + } + + bc_decompose(from_mat, loc, rot, NULL, scale); + loc_eulO_size_to_mat4(to_mat, loc, rot, scale, 6); + + if (export_settings.get_keep_bind_info()) { + bc_get_property_vector(bone, "restpose_loc", loc, loc); + + if (use_local_space && bone->parent) { + Bone *b = bone; + while (b->parent) { + b = b->parent; + float ploc[3]; + bc_get_property_vector(b, "restpose_loc", ploc, V0); + loc[0] += ploc[0]; + loc[1] += ploc[1]; + loc[2] += ploc[2]; + } + } + } + + if (export_settings.get_keep_bind_info()) { + if (bc_get_IDProperty(bone, "restpose_rot_x")) { + rot[0] = DEG2RADF(bc_get_property(bone, "restpose_rot_x", 0)); + } + if (bc_get_IDProperty(bone, "restpose_rot_y")) { + rot[1] = DEG2RADF(bc_get_property(bone, "restpose_rot_y", 0)); + } + if (bc_get_IDProperty(bone, "restpose_rot_z")) { + rot[2] = DEG2RADF(bc_get_property(bone, "restpose_rot_z", 0)); + } + } + + if (export_settings.get_keep_bind_info()) { + bc_get_property_vector(bone, "restpose_scale", scale, scale); + } + + loc_eulO_size_to_mat4(to_mat, loc, rot, scale, 6); +} + +void bc_sanitize_v3(float v[3], int precision) +{ + for (int i = 0; i < 3; i++) { + double val = (double)v[i]; + val = double_round(val, precision); + v[i] = (float)val; + } +} + +void bc_sanitize_v3(double v[3], int precision) +{ + for (int i = 0; i < 3; i++) { + v[i] = double_round(v[i], precision); + } +} + +void bc_copy_m4_farray(float r[4][4], float *a) +{ + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + r[i][j] = *a++; + } + } +} + +void bc_copy_farray_m4(float *r, float a[4][4]) +{ + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + *r++ = a[i][j]; + } + } +} + +void bc_copy_darray_m4d(double *r, double a[4][4]) +{ + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + *r++ = a[i][j]; + } + } +} + +void bc_copy_v44_m4d(std::vector<std::vector<double>> &r, double (&a)[4][4]) +{ + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + r[i][j] = a[i][j]; + } + } +} + +void bc_copy_m4d_v44(double (&r)[4][4], std::vector<std::vector<double>> &a) +{ + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + r[i][j] = a[i][j]; + } + } +} + +/** + * Returns name of Active UV Layer or empty String if no active UV Layer defined + */ +static std::string bc_get_active_uvlayer_name(Mesh *me) +{ + int num_layers = CustomData_number_of_layers(&me->ldata, CD_MLOOPUV); + if (num_layers) { + char *layer_name = bc_CustomData_get_active_layer_name(&me->ldata, CD_MLOOPUV); + if (layer_name) { + return std::string(layer_name); + } + } + return ""; +} + +/** + * Returns name of Active UV Layer or empty String if no active UV Layer defined. + * Assuming the Object is of type MESH + */ +static std::string bc_get_active_uvlayer_name(Object *ob) +{ + Mesh *me = (Mesh *)ob->data; + return bc_get_active_uvlayer_name(me); +} + +/** + * Returns UV Layer name or empty string if layer index is out of range + */ +static std::string bc_get_uvlayer_name(Mesh *me, int layer) +{ + int num_layers = CustomData_number_of_layers(&me->ldata, CD_MLOOPUV); + if (num_layers && layer < num_layers) { + char *layer_name = bc_CustomData_get_layer_name(&me->ldata, CD_MLOOPUV, layer); + if (layer_name) { + return std::string(layer_name); + } + } + return ""; +} + +std::string bc_find_bonename_in_path(std::string path, std::string probe) +{ + std::string result; + char *boneName = BLI_str_quoted_substrN(path.c_str(), probe.c_str()); + if (boneName) { + result = std::string(boneName); + MEM_freeN(boneName); + } + return result; +} + +static bNodeTree *prepare_material_nodetree(Material *ma) +{ + if (ma->nodetree == NULL) { + ma->nodetree = ntreeAddTree(NULL, "Shader Nodetree", "ShaderNodeTree"); + ma->use_nodes = true; + } + return ma->nodetree; +} + +static bNode *bc_add_node( + bContext *C, bNodeTree *ntree, int node_type, int locx, int locy, std::string label) +{ + bNode *node = nodeAddStaticNode(C, ntree, node_type); + if (node) { + if (label.length() > 0) { + strcpy(node->label, label.c_str()); + } + node->locx = locx; + node->locy = locy; + node->flag |= NODE_SELECT; + } + return node; +} + +static bNode *bc_add_node(bContext *C, bNodeTree *ntree, int node_type, int locx, int locy) +{ + return bc_add_node(C, ntree, node_type, locx, locy, ""); +} + +#if 0 +/* experimental, probably not used */ +static bNodeSocket *bc_group_add_input_socket(bNodeTree *ntree, + bNode *to_node, + int to_index, + std::string label) +{ + bNodeSocket *to_socket = (bNodeSocket *)BLI_findlink(&to_node->inputs, to_index); + + //bNodeSocket *socket = ntreeAddSocketInterfaceFromSocket(ntree, to_node, to_socket); + //return socket; + + bNodeSocket *gsock = ntreeAddSocketInterfaceFromSocket(ntree, to_node, to_socket); + bNode *inputGroup = ntreeFindType(ntree, NODE_GROUP_INPUT); + node_group_input_verify(ntree, inputGroup, (ID *)ntree); + bNodeSocket *newsock = node_group_input_find_socket(inputGroup, gsock->identifier); + nodeAddLink(ntree, inputGroup, newsock, to_node, to_socket); + strcpy(newsock->name, label.c_str()); + return newsock; +} + +static bNodeSocket *bc_group_add_output_socket(bNodeTree *ntree, + bNode *from_node, + int from_index, + std::string label) +{ + bNodeSocket *from_socket = (bNodeSocket *)BLI_findlink(&from_node->outputs, from_index); + + //bNodeSocket *socket = ntreeAddSocketInterfaceFromSocket(ntree, to_node, to_socket); + //return socket; + + bNodeSocket *gsock = ntreeAddSocketInterfaceFromSocket(ntree, from_node, from_socket); + bNode *outputGroup = ntreeFindType(ntree, NODE_GROUP_OUTPUT); + node_group_output_verify(ntree, outputGroup, (ID *)ntree); + bNodeSocket *newsock = node_group_output_find_socket(outputGroup, gsock->identifier); + nodeAddLink(ntree, from_node, from_socket, outputGroup, newsock); + strcpy(newsock->name, label.c_str()); + return newsock; +} + +void bc_make_group(bContext *C, bNodeTree *ntree, std::map<std::string, bNode *> nmap) +{ + bNode *gnode = node_group_make_from_selected(C, ntree, "ShaderNodeGroup", "ShaderNodeTree"); + bNodeTree *gtree = (bNodeTree *)gnode->id; + + bc_group_add_input_socket(gtree, nmap["main"], 0, "Diffuse"); + bc_group_add_input_socket(gtree, nmap["emission"], 0, "Emission"); + bc_group_add_input_socket(gtree, nmap["mix"], 0, "Transparency"); + bc_group_add_input_socket(gtree, nmap["emission"], 1, "Emission"); + bc_group_add_input_socket(gtree, nmap["main"], 4, "Metallic"); + bc_group_add_input_socket(gtree, nmap["main"], 5, "Specular"); + + bc_group_add_output_socket(gtree, nmap["mix"], 0, "Shader"); +} +#endif + +static void bc_node_add_link( + bNodeTree *ntree, bNode *from_node, int from_index, bNode *to_node, int to_index) +{ + bNodeSocket *from_socket = (bNodeSocket *)BLI_findlink(&from_node->outputs, from_index); + bNodeSocket *to_socket = (bNodeSocket *)BLI_findlink(&to_node->inputs, to_index); + + nodeAddLink(ntree, from_node, from_socket, to_node, to_socket); +} + +void bc_add_default_shader(bContext *C, Material *ma) +{ + bNodeTree *ntree = prepare_material_nodetree(ma); + std::map<std::string, bNode *> nmap; +#if 0 + nmap["main"] = bc_add_node(C, ntree, SH_NODE_BSDF_PRINCIPLED, -300, 300); + nmap["emission"] = bc_add_node(C, ntree, SH_NODE_EMISSION, -300, 500, "emission"); + nmap["add"] = bc_add_node(C, ntree, SH_NODE_ADD_SHADER, 100, 400); + nmap["transparent"] = bc_add_node(C, ntree, SH_NODE_BSDF_TRANSPARENT, 100, 200); + nmap["mix"] = bc_add_node(C, ntree, SH_NODE_MIX_SHADER, 400, 300, "transparency"); + nmap["out"] = bc_add_node(C, ntree, SH_NODE_OUTPUT_MATERIAL, 600, 300); + nmap["out"]->flag &= ~NODE_SELECT; + + bc_node_add_link(ntree, nmap["emission"], 0, nmap["add"], 0); + bc_node_add_link(ntree, nmap["main"], 0, nmap["add"], 1); + bc_node_add_link(ntree, nmap["add"], 0, nmap["mix"], 1); + bc_node_add_link(ntree, nmap["transparent"], 0, nmap["mix"], 2); + + bc_node_add_link(ntree, nmap["mix"], 0, nmap["out"], 0); + /* experimental, probably not used. */ + bc_make_group(C, ntree, nmap); +#else + nmap["main"] = bc_add_node(C, ntree, SH_NODE_BSDF_PRINCIPLED, 0, 300); + nmap["out"] = bc_add_node(C, ntree, SH_NODE_OUTPUT_MATERIAL, 300, 300); + bc_node_add_link(ntree, nmap["main"], 0, nmap["out"], 0); +#endif +} + +COLLADASW::ColorOrTexture bc_get_base_color(Material *ma) +{ + /* for alpha see bc_get_alpha() */ + Color default_color = {ma->r, ma->g, ma->b, 1.0}; + bNode *shader = bc_get_master_shader(ma); + if (ma->use_nodes && shader) { + return bc_get_cot_from_shader(shader, "Base Color", default_color, false); + } + else { + return bc_get_cot(default_color); + } +} + +COLLADASW::ColorOrTexture bc_get_emission(Material *ma) +{ + Color default_color = {0, 0, 0, 1}; + bNode *shader = bc_get_master_shader(ma); + if (ma->use_nodes && shader) { + return bc_get_cot_from_shader(shader, "Emission", default_color); + } + else { + return bc_get_cot(default_color); /* default black */ + } +} + +COLLADASW::ColorOrTexture bc_get_ambient(Material *ma) +{ + Color default_color = {0, 0, 0, 1.0}; + return bc_get_cot(default_color); +} + +COLLADASW::ColorOrTexture bc_get_specular(Material *ma) +{ + Color default_color = {0, 0, 0, 1.0}; + return bc_get_cot(default_color); +} + +COLLADASW::ColorOrTexture bc_get_reflective(Material *ma) +{ + Color default_color = {0, 0, 0, 1.0}; + return bc_get_cot(default_color); +} + +double bc_get_alpha(Material *ma) +{ + double alpha = ma->a; /* fallback if no socket found */ + bNode *master_shader = bc_get_master_shader(ma); + if (ma->use_nodes && master_shader) { + bc_get_float_from_shader(master_shader, alpha, "Alpha"); + } + return alpha; +} + +double bc_get_ior(Material *ma) +{ + double ior = -1; /* fallback if no socket found */ + bNode *master_shader = bc_get_master_shader(ma); + if (ma->use_nodes && master_shader) { + bc_get_float_from_shader(master_shader, ior, "IOR"); + } + return ior; +} + +double bc_get_shininess(Material *ma) +{ + double ior = -1; /* fallback if no socket found */ + bNode *master_shader = bc_get_master_shader(ma); + if (ma->use_nodes && master_shader) { + bc_get_float_from_shader(master_shader, ior, "Roughness"); + } + return ior; +} + +double bc_get_reflectivity(Material *ma) +{ + double reflectivity = ma->spec; /* fallback if no socket found */ + bNode *master_shader = bc_get_master_shader(ma); + if (ma->use_nodes && master_shader) { + bc_get_float_from_shader(master_shader, reflectivity, "Metallic"); + } + return reflectivity; +} + +double bc_get_float_from_shader(bNode *shader, double &val, std::string nodeid) +{ + bNodeSocket *socket = nodeFindSocket(shader, SOCK_IN, nodeid.c_str()); + if (socket) { + bNodeSocketValueFloat *ref = (bNodeSocketValueFloat *)socket->default_value; + val = (double)ref->value; + return true; + } + return false; +} + +COLLADASW::ColorOrTexture bc_get_cot_from_shader(bNode *shader, + std::string nodeid, + Color &default_color, + bool with_alpha) +{ + bNodeSocket *socket = nodeFindSocket(shader, SOCK_IN, nodeid.c_str()); + if (socket) { + bNodeSocketValueRGBA *dcol = (bNodeSocketValueRGBA *)socket->default_value; + float *col = dcol->value; + return bc_get_cot(col, with_alpha); + } + else { + return bc_get_cot(default_color, with_alpha); + } +} + +bNode *bc_get_master_shader(Material *ma) +{ + bNodeTree *nodetree = ma->nodetree; + if (nodetree) { + for (bNode *node = (bNode *)nodetree->nodes.first; node; node = node->next) { + if (node->typeinfo->type == SH_NODE_BSDF_PRINCIPLED) { + return node; + } + } + } + return NULL; +} + +COLLADASW::ColorOrTexture bc_get_cot(float r, float g, float b, float a) +{ + COLLADASW::Color color(r, g, b, a); + COLLADASW::ColorOrTexture cot(color); + return cot; +} + +COLLADASW::ColorOrTexture bc_get_cot(Color col, bool with_alpha) +{ + COLLADASW::Color color(col[0], col[1], col[2], (with_alpha) ? col[3] : 1.0); + COLLADASW::ColorOrTexture cot(color); + return cot; +} diff --git a/source/blender/io/collada/collada_utils.h b/source/blender/io/collada/collada_utils.h new file mode 100644 index 00000000000..5c5e1415422 --- /dev/null +++ b/source/blender/io/collada/collada_utils.h @@ -0,0 +1,399 @@ +/* + * 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 collada + */ + +#ifndef __COLLADA_UTILS_H__ +#define __COLLADA_UTILS_H__ + +#include "COLLADAFWMeshPrimitive.h" +#include "COLLADAFWGeometry.h" +#include "COLLADAFWFloatOrDoubleArray.h" +#include "COLLADAFWTypes.h" +#include "COLLADASWEffectProfile.h" +#include "COLLADAFWColorOrTexture.h" + +#include <vector> +#include <map> +#include <set> +#include <algorithm> + +extern "C" { +#include "DNA_object_types.h" +#include "DNA_anim_types.h" +#include "DNA_constraint_types.h" +#include "DNA_mesh_types.h" +#include "DNA_light_types.h" +#include "DNA_camera_types.h" + +#include "DNA_customdata_types.h" +#include "DNA_texture_types.h" +#include "DNA_scene_types.h" + +#include "RNA_access.h" + +#include "BLI_linklist.h" +#include "BLI_utildefines.h" +#include "BLI_string.h" + +#include "BKE_main.h" +#include "BKE_context.h" +#include "BKE_object.h" +#include "BKE_scene.h" +#include "BKE_idprop.h" +#include "BKE_node.h" +} + +#include "DEG_depsgraph_query.h" + +#include "ImportSettings.h" +#include "ExportSettings.h" +#include "collada_internal.h" +#include "BCSampleData.h" +#include "BlenderContext.h" + +constexpr int LIMITTED_PRECISION = 6; + +typedef std::map<COLLADAFW::UniqueId, Image *> UidImageMap; +typedef std::map<std::string, Image *> KeyImageMap; +typedef std::map<COLLADAFW::TextureMapId, std::vector<MTex *>> TexIndexTextureArrayMap; +typedef std::set<Object *> BCObjectSet; + +extern void bc_update_scene(BlenderContext &blender_context, float ctime); + +/* Action helpers */ + +std::vector<bAction *> bc_getSceneActions(const bContext *C, Object *ob, bool all_actions); + +/* Action helpers */ + +inline bAction *bc_getSceneObjectAction(Object *ob) +{ + return (ob->adt && ob->adt->action) ? ob->adt->action : NULL; +} + +/* Returns Light Action or NULL */ +inline bAction *bc_getSceneLightAction(Object *ob) +{ + if (ob->type != OB_LAMP) { + return NULL; + } + + Light *lamp = (Light *)ob->data; + return (lamp->adt && lamp->adt->action) ? lamp->adt->action : NULL; +} + +/* Return Camera Action or NULL */ +inline bAction *bc_getSceneCameraAction(Object *ob) +{ + if (ob->type != OB_CAMERA) { + return NULL; + } + + Camera *camera = (Camera *)ob->data; + return (camera->adt && camera->adt->action) ? camera->adt->action : NULL; +} + +/* returns material action or NULL */ +inline bAction *bc_getSceneMaterialAction(Material *ma) +{ + if (ma == NULL) { + return NULL; + } + + return (ma->adt && ma->adt->action) ? ma->adt->action : NULL; +} + +std::string bc_get_action_id(std::string action_name, + std::string ob_name, + std::string channel_type, + std::string axis_name, + std::string axis_separator = "_"); + +extern float bc_get_float_value(const COLLADAFW::FloatOrDoubleArray &array, unsigned int index); +extern int bc_test_parent_loop(Object *par, Object *ob); + +extern bool bc_validateConstraints(bConstraint *con); + +bool bc_set_parent(Object *ob, Object *par, bContext *C, bool is_parent_space = true); +extern Object *bc_add_object( + Main *bmain, Scene *scene, ViewLayer *view_layer, int type, const char *name); +extern Mesh *bc_get_mesh_copy(BlenderContext &blender_context, + Object *ob, + BC_export_mesh_type export_mesh_type, + bool apply_modifiers, + bool triangulate); + +extern Object *bc_get_assigned_armature(Object *ob); +extern bool bc_has_object_type(LinkNode *export_set, short obtype); + +extern char *bc_CustomData_get_layer_name(const CustomData *data, int type, int n); +extern char *bc_CustomData_get_active_layer_name(const CustomData *data, int type); + +extern void bc_bubble_sort_by_Object_name(LinkNode *export_set); +extern bool bc_is_root_bone(Bone *aBone, bool deform_bones_only); +extern int bc_get_active_UVLayer(Object *ob); + +std::string bc_find_bonename_in_path(std::string path, std::string probe); + +inline std::string bc_string_after(const std::string &s, const std::string probe) +{ + size_t i = s.rfind(probe); + if (i != std::string::npos) { + return (s.substr(i + probe.length(), s.length() - i)); + } + return (s); +} + +inline std::string bc_string_before(const std::string &s, const std::string probe) +{ + size_t i = s.find(probe); + if (i != std::string::npos) { + return s.substr(0, i); + } + return (s); +} + +inline bool bc_startswith(std::string const &value, std::string const &starting) +{ + if (starting.size() > value.size()) { + return false; + } + return (value.substr(0, starting.size()) == starting); +} + +inline bool bc_endswith(const std::string &value, const std::string &ending) +{ + if (ending.size() > value.size()) { + return false; + } + + return value.compare(value.size() - ending.size(), ending.size(), ending) == 0; +} + +#if 0 /* UNUSED */ +inline bool bc_endswith(std::string const &value, std::string const &ending) +{ + if (ending.size() > value.size()) { + return false; + } + return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); +} +#endif + +extern std::string bc_replace_string(std::string data, + const std::string &pattern, + const std::string &replacement); +extern std::string bc_url_encode(std::string data); +extern void bc_match_scale(Object *ob, UnitConverter &bc_unit, bool scale_to_scene); +extern void bc_match_scale(std::vector<Object *> *objects_done, + UnitConverter &unit_converter, + bool scale_to_scene); + +extern void bc_decompose(float mat[4][4], float *loc, float eul[3], float quat[4], float *size); +extern void bc_rotate_from_reference_quat(float quat_to[4], + float quat_from[4], + float mat_to[4][4]); + +extern void bc_triangulate_mesh(Mesh *me); +extern bool bc_is_leaf_bone(Bone *bone); +extern EditBone *bc_get_edit_bone(bArmature *armature, char *name); +extern int bc_set_layer(int bitfield, int layer, bool enable); +extern int bc_set_layer(int bitfield, int layer); + +inline bool bc_in_range(float a, float b, float range) +{ + return fabsf(a - b) < range; +} +void bc_copy_m4_farray(float r[4][4], float *a); +void bc_copy_farray_m4(float *r, float a[4][4]); +void bc_copy_darray_m4d(double *r, double a[4][4]); +void bc_copy_m4d_v44(double (&r)[4][4], std::vector<std::vector<double>> &a); +void bc_copy_v44_m4d(std::vector<std::vector<double>> &a, double (&r)[4][4]); + +void bc_sanitize_v3(double v[3], int precision); +void bc_sanitize_v3(float v[3], int precision); + +extern IDProperty *bc_get_IDProperty(Bone *bone, std::string key); +extern void bc_set_IDProperty(EditBone *ebone, const char *key, float value); +extern void bc_set_IDPropertyMatrix(EditBone *ebone, const char *key, float mat[4][4]); + +extern float bc_get_property(Bone *bone, std::string key, float def); +extern void bc_get_property_vector(Bone *bone, std::string key, float val[3], const float def[3]); +extern bool bc_get_property_matrix(Bone *bone, std::string key, float mat[4][4]); + +extern void bc_enable_fcurves(bAction *act, char *bone_name); +extern bool bc_bone_matrix_local_get(Object *ob, Bone *bone, Matrix &mat, bool for_opensim); +extern bool bc_is_animated(BCMatrixSampleMap &values); +extern bool bc_has_animations(Scene *sce, LinkNode *node); +extern bool bc_has_animations(Object *ob); + +extern void bc_add_global_transform(Matrix &to_mat, + const Matrix &from_mat, + const BCMatrix &global_transform, + const bool invert = false); +extern void bc_add_global_transform(Vector &to_vec, + const Vector &from_vec, + const BCMatrix &global_transform, + const bool invert = false); +extern void bc_add_global_transform(Vector &to_vec, + const BCMatrix &global_transform, + const bool invert = false); +extern void bc_add_global_transform(Matrix &to_mat, + const BCMatrix &global_transform, + const bool invert = false); +extern void bc_apply_global_transform(Matrix &to_mat, + const BCMatrix &global_transform, + const bool invert = false); +extern void bc_apply_global_transform(Vector &to_vec, + const BCMatrix &global_transform, + const bool invert = false); +extern void bc_create_restpose_mat(BCExportSettings &export_settings, + Bone *bone, + float to_mat[4][4], + float from_mat[4][4], + bool use_local_space); + +class ColladaBaseNodes { + private: + std::vector<Object *> base_objects; + + public: + void add(Object *ob) + { + base_objects.push_back(ob); + } + + bool contains(Object *ob) + { + std::vector<Object *>::iterator it = std::find(base_objects.begin(), base_objects.end(), ob); + return (it != base_objects.end()); + } + + int size() + { + return base_objects.size(); + } + + Object *get(int index) + { + return base_objects[index]; + } +}; + +class BCPolygonNormalsIndices { + std::vector<unsigned int> normal_indices; + + public: + void add_index(unsigned int index) + { + normal_indices.push_back(index); + } + + unsigned int operator[](unsigned int i) + { + return normal_indices[i]; + } +}; + +class BoneExtended { + + private: + char name[MAXBONENAME]; + int chain_length; + bool is_leaf; + float tail[3]; + float roll; + + int bone_layers; + int use_connect; + bool has_custom_tail; + bool has_custom_roll; + + public: + BoneExtended(EditBone *aBone); + + void set_name(char *aName); + char *get_name(); + + void set_chain_length(const int aLength); + int get_chain_length(); + + void set_leaf_bone(bool state); + bool is_leaf_bone(); + + void set_bone_layers(std::string layers, std::vector<std::string> &layer_labels); + int get_bone_layers(); + static std::string get_bone_layers(int bitfield); + + void set_roll(float roll); + bool has_roll(); + float get_roll(); + + void set_tail(float vec[]); + float *get_tail(); + bool has_tail(); + + void set_use_connect(int use_connect); + int get_use_connect(); +}; + +/* a map to store bone extension maps + * std:string : an armature name + * BoneExtended * : a map that contains extra data for bones + */ +typedef std::map<std::string, BoneExtended *> BoneExtensionMap; + +/* + * A class to organize bone extension data for multiple Armatures. + * this is needed for the case where a Collada file contains 2 or more + * separate armatures. + */ +class BoneExtensionManager { + private: + std::map<std::string, BoneExtensionMap *> extended_bone_maps; + + public: + BoneExtensionMap &getExtensionMap(bArmature *armature); + ~BoneExtensionManager(); +}; + +void bc_add_default_shader(bContext *C, Material *ma); +bNode *bc_get_master_shader(Material *ma); + +COLLADASW::ColorOrTexture bc_get_base_color(Material *ma); +COLLADASW::ColorOrTexture bc_get_emission(Material *ma); +COLLADASW::ColorOrTexture bc_get_ambient(Material *ma); +COLLADASW::ColorOrTexture bc_get_specular(Material *ma); +COLLADASW::ColorOrTexture bc_get_reflective(Material *ma); + +double bc_get_reflectivity(Material *ma); +double bc_get_alpha(Material *ma); +double bc_get_ior(Material *ma); +double bc_get_shininess(Material *ma); + +double bc_get_float_from_shader(bNode *shader, double &ior, std::string nodeid); +COLLADASW::ColorOrTexture bc_get_cot_from_shader(bNode *shader, + std::string nodeid, + Color &default_color, + bool with_alpha = true); + +COLLADASW::ColorOrTexture bc_get_cot(float r, float g, float b, float a); +COLLADASW::ColorOrTexture bc_get_cot(Color col, bool with_alpha = true); + +#endif diff --git a/source/blender/io/collada/version.conf b/source/blender/io/collada/version.conf new file mode 100644 index 00000000000..d39af7a53df --- /dev/null +++ b/source/blender/io/collada/version.conf @@ -0,0 +1 @@ +463ba8a2ef5a021ce21df614dde29e0ee800e10b diff --git a/source/blender/io/usd/CMakeLists.txt b/source/blender/io/usd/CMakeLists.txt new file mode 100644 index 00000000000..732a638a255 --- /dev/null +++ b/source/blender/io/usd/CMakeLists.txt @@ -0,0 +1,111 @@ +# ***** 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) 2019, Blender Foundation +# All rights reserved. +# ***** END GPL LICENSE BLOCK ***** + +# This suppresses the warning "This file includes at least one deprecated or antiquated +# header which may be removed without further notice at a future date", which is caused +# by the USD library including <ext/hash_set> on Linux. This has been reported at: +# https://github.com/PixarAnimationStudios/USD/issues/1057. +if(UNIX AND NOT APPLE) + add_definitions(-D_GLIBCXX_PERMIT_BACKWARD_HASH) +endif() +if(WIN32) + add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN) +endif() +add_definitions(-DPXR_STATIC) + +set(INC + . + ../../blenkernel + ../../blenlib + ../../blenloader + ../../bmesh + ../../depsgraph + ../../editors/include + ../../makesdna + ../../makesrna + ../../windowmanager + ../../../../intern/guardedalloc + ../../../../intern/utfconv +) + +set(INC_SYS + ${USD_INCLUDE_DIRS} + ${BOOST_INCLUDE_DIR} + ${TBB_INCLUDE_DIR} +) + +set(SRC + intern/abstract_hierarchy_iterator.cc + intern/usd_capi.cc + intern/usd_hierarchy_iterator.cc + intern/usd_writer_abstract.cc + intern/usd_writer_camera.cc + intern/usd_writer_hair.cc + intern/usd_writer_light.cc + intern/usd_writer_mesh.cc + intern/usd_writer_metaball.cc + intern/usd_writer_transform.cc + + usd.h + intern/abstract_hierarchy_iterator.h + intern/usd_exporter_context.h + intern/usd_hierarchy_iterator.h + intern/usd_writer_abstract.h + intern/usd_writer_camera.h + intern/usd_writer_hair.h + intern/usd_writer_light.h + intern/usd_writer_mesh.h + intern/usd_writer_metaball.h + intern/usd_writer_transform.h +) + +set(LIB + bf_blenkernel + bf_blenlib +) + +list(APPEND LIB + ${BOOST_LIBRARIES} +) + +list(APPEND LIB +) + +blender_add_lib(bf_usd "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") + +if(WIN32) + set_property(TARGET bf_usd APPEND_STRING PROPERTY LINK_FLAGS_DEBUG " /WHOLEARCHIVE:${USD_DEBUG_LIB}") + set_property(TARGET bf_usd APPEND_STRING PROPERTY LINK_FLAGS_RELEASE " /WHOLEARCHIVE:${USD_RELEASE_LIB}") + set_property(TARGET bf_usd APPEND_STRING PROPERTY LINK_FLAGS_RELWITHDEBINFO " /WHOLEARCHIVE:${USD_RELEASE_LIB}") + set_property(TARGET bf_usd APPEND_STRING PROPERTY LINK_FLAGS_MINSIZEREL " /WHOLEARCHIVE:${USD_RELEASE_LIB}") +endif() + +# Source: https://github.com/PixarAnimationStudios/USD/blob/master/BUILDING.md#linking-whole-archives +if(WIN32) + target_link_libraries(bf_usd INTERFACE ${USD_LIBRARIES}) +elseif(CMAKE_COMPILER_IS_GNUCXX) + target_link_libraries(bf_usd INTERFACE "-Wl,--whole-archive ${USD_LIBRARIES} -Wl,--no-whole-archive ${TBB_LIBRARIES}") +elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + target_link_libraries(bf_usd INTERFACE -Wl,-force_load ${USD_LIBRARIES}) +else() + message(FATAL_ERROR "Unknown how to link USD with your compiler ${CMAKE_CXX_COMPILER_ID}") +endif() + +target_link_libraries(bf_usd INTERFACE ${TBB_LIBRARIES}) diff --git a/source/blender/io/usd/intern/abstract_hierarchy_iterator.cc b/source/blender/io/usd/intern/abstract_hierarchy_iterator.cc new file mode 100644 index 00000000000..a8ed2c5f2a5 --- /dev/null +++ b/source/blender/io/usd/intern/abstract_hierarchy_iterator.cc @@ -0,0 +1,595 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ +#include "abstract_hierarchy_iterator.h" + +#include <iostream> +#include <limits.h> +#include <sstream> +#include <string> + +extern "C" { +#include "BKE_anim.h" +#include "BKE_particle.h" + +#include "BLI_assert.h" +#include "BLI_listbase.h" +#include "BLI_math_matrix.h" + +#include "DNA_ID.h" +#include "DNA_layer_types.h" +#include "DNA_object_types.h" +#include "DNA_particle_types.h" + +#include "DEG_depsgraph_query.h" +} + +namespace USD { + +const HierarchyContext *HierarchyContext::root() +{ + return nullptr; +} + +bool HierarchyContext::operator<(const HierarchyContext &other) const +{ + if (object != other.object) { + return object < other.object; + } + if (duplicator != NULL && duplicator == other.duplicator) { + // Only resort to string comparisons when both objects are created by the same duplicator. + return export_name < other.export_name; + } + + return export_parent < other.export_parent; +} + +bool HierarchyContext::is_instance() const +{ + return !original_export_path.empty(); +} +void HierarchyContext::mark_as_instance_of(const std::string &reference_export_path) +{ + original_export_path = reference_export_path; +} +void HierarchyContext::mark_as_not_instanced() +{ + original_export_path.clear(); +} + +AbstractHierarchyWriter::~AbstractHierarchyWriter() +{ +} + +AbstractHierarchyIterator::AbstractHierarchyIterator(Depsgraph *depsgraph) + : depsgraph_(depsgraph), writers_() +{ +} + +AbstractHierarchyIterator::~AbstractHierarchyIterator() +{ +} + +void AbstractHierarchyIterator::iterate_and_write() +{ + export_graph_construct(); + connect_loose_objects(); + export_graph_prune(); + determine_export_paths(HierarchyContext::root()); + determine_duplication_references(HierarchyContext::root(), ""); + make_writers(HierarchyContext::root()); + export_graph_clear(); +} + +void AbstractHierarchyIterator::release_writers() +{ + for (WriterMap::value_type it : writers_) { + delete_object_writer(it.second); + } + writers_.clear(); +} + +std::string AbstractHierarchyIterator::make_valid_name(const std::string &name) const +{ + return name; +} + +std::string AbstractHierarchyIterator::get_id_name(const ID *id) const +{ + if (id == nullptr) { + return ""; + } + + return make_valid_name(std::string(id->name + 2)); +} + +std::string AbstractHierarchyIterator::get_object_data_path(const HierarchyContext *context) const +{ + BLI_assert(!context->export_path.empty()); + BLI_assert(context->object->data); + + return path_concatenate(context->export_path, get_object_data_name(context->object)); +} + +void AbstractHierarchyIterator::debug_print_export_graph(const ExportGraph &graph) const +{ + size_t total_graph_size = 0; + for (const ExportGraph::value_type &map_iter : graph) { + const DupliAndDuplicator &parent_info = map_iter.first; + Object *const export_parent = parent_info.first; + Object *const duplicator = parent_info.second; + + if (duplicator != nullptr) { + printf(" DU %s (as dupped by %s):\n", + export_parent == nullptr ? "-null-" : (export_parent->id.name + 2), + duplicator->id.name + 2); + } + else { + printf(" OB %s:\n", export_parent == nullptr ? "-null-" : (export_parent->id.name + 2)); + } + + total_graph_size += map_iter.second.size(); + for (HierarchyContext *child_ctx : map_iter.second) { + if (child_ctx->duplicator == nullptr) { + printf(" - %s%s%s\n", + child_ctx->object->id.name + 2, + child_ctx->weak_export ? " (weak)" : "", + child_ctx->original_export_path.size() ? + (std::string("ref ") + child_ctx->original_export_path).c_str() : + ""); + } + else { + printf(" - %s (dup by %s%s) %s\n", + child_ctx->object->id.name + 2, + child_ctx->duplicator->id.name + 2, + child_ctx->weak_export ? ", weak" : "", + child_ctx->original_export_path.size() ? + (std::string("ref ") + child_ctx->original_export_path).c_str() : + ""); + } + } + } + printf(" (Total graph size: %lu objects\n", total_graph_size); +} + +void AbstractHierarchyIterator::export_graph_construct() +{ + Scene *scene = DEG_get_evaluated_scene(depsgraph_); + + DEG_OBJECT_ITER_BEGIN (depsgraph_, + object, + DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY | + DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET) { + // Non-instanced objects always have their object-parent as export-parent. + const bool weak_export = mark_as_weak_export(object); + visit_object(object, object->parent, weak_export); + + if (weak_export) { + // If a duplicator shouldn't be exported, its duplilist also shouldn't be. + continue; + } + + // Export the duplicated objects instanced by this object. + ListBase *lb = object_duplilist(depsgraph_, scene, object); + if (lb) { + // Construct the set of duplicated objects, so that later we can determine whether a parent + // is also duplicated itself. + std::set<Object *> dupli_set; + LISTBASE_FOREACH (DupliObject *, dupli_object, lb) { + if (!should_visit_dupli_object(dupli_object)) { + continue; + } + dupli_set.insert(dupli_object->ob); + } + + LISTBASE_FOREACH (DupliObject *, dupli_object, lb) { + if (!should_visit_dupli_object(dupli_object)) { + continue; + } + + visit_dupli_object(dupli_object, object, dupli_set); + } + } + + free_object_duplilist(lb); + } + DEG_OBJECT_ITER_END; +} + +void AbstractHierarchyIterator::connect_loose_objects() +{ + // Find those objects whose parent is not part of the export graph; these + // objects would be skipped when traversing the graph as a hierarchy. + // These objects will have to be re-attached to some parent object in order to + // fit into the hierarchy. + ExportGraph loose_objects_graph = export_graph_; + for (const ExportGraph::value_type &map_iter : export_graph_) { + for (const HierarchyContext *child : map_iter.second) { + // An object that is marked as a child of another object is not considered 'loose'. + loose_objects_graph.erase(std::make_pair(child->object, child->duplicator)); + } + } + // The root of the hierarchy is always found, so it's never considered 'loose'. + loose_objects_graph.erase(std::make_pair(nullptr, nullptr)); + + // Iterate over the loose objects and connect them to their export parent. + for (const ExportGraph::value_type &map_iter : loose_objects_graph) { + const DupliAndDuplicator &export_info = map_iter.first; + Object *object = export_info.first; + Object *export_parent = object->parent; + + while (true) { + // Loose objects will all be real objects, as duplicated objects always have + // their duplicator or other exported duplicated object as ancestor. + ExportGraph::iterator found_parent_iter = export_graph_.find( + std::make_pair(export_parent, nullptr)); + + visit_object(object, export_parent, true); + if (found_parent_iter != export_graph_.end()) { + break; + } + // 'export_parent' will never be nullptr here, as the export graph contains the + // tuple <nullptr, nullptr> as root and thus will cause a break. + BLI_assert(export_parent != nullptr); + + object = export_parent; + export_parent = export_parent->parent; + } + } +} + +static bool remove_weak_subtrees(const HierarchyContext *context, + AbstractHierarchyIterator::ExportGraph &clean_graph, + const AbstractHierarchyIterator::ExportGraph &input_graph) +{ + bool all_is_weak = context != nullptr && context->weak_export; + Object *object = context != nullptr ? context->object : nullptr; + Object *duplicator = context != nullptr ? context->duplicator : nullptr; + + const AbstractHierarchyIterator::DupliAndDuplicator map_key = std::make_pair(object, duplicator); + AbstractHierarchyIterator::ExportGraph::const_iterator child_iterator; + + child_iterator = input_graph.find(map_key); + if (child_iterator != input_graph.end()) { + for (HierarchyContext *child_context : child_iterator->second) { + bool child_tree_is_weak = remove_weak_subtrees(child_context, clean_graph, input_graph); + all_is_weak &= child_tree_is_weak; + + if (child_tree_is_weak) { + // This subtree is all weak, so we can remove it from the current object's children. + clean_graph[map_key].erase(child_context); + delete child_context; + } + } + } + + if (all_is_weak) { + // This node and all its children are weak, so it can be removed from the export graph. + clean_graph.erase(map_key); + } + + return all_is_weak; +} + +void AbstractHierarchyIterator::export_graph_prune() +{ + // Take a copy of the map so that we can modify while recursing. + ExportGraph unpruned_export_graph = export_graph_; + remove_weak_subtrees(HierarchyContext::root(), export_graph_, unpruned_export_graph); +} + +void AbstractHierarchyIterator::export_graph_clear() +{ + for (ExportGraph::iterator::value_type &it : export_graph_) { + for (HierarchyContext *context : it.second) { + delete context; + } + } + export_graph_.clear(); +} + +void AbstractHierarchyIterator::visit_object(Object *object, + Object *export_parent, + bool weak_export) +{ + HierarchyContext *context = new HierarchyContext(); + context->object = object; + context->export_name = get_object_name(object); + context->export_parent = export_parent; + context->duplicator = nullptr; + context->weak_export = weak_export; + context->animation_check_include_parent = false; + context->export_path = ""; + context->original_export_path = ""; + copy_m4_m4(context->matrix_world, object->obmat); + + export_graph_[std::make_pair(export_parent, nullptr)].insert(context); +} + +void AbstractHierarchyIterator::visit_dupli_object(DupliObject *dupli_object, + Object *duplicator, + const std::set<Object *> &dupli_set) +{ + ExportGraph::key_type graph_index; + bool animation_check_include_parent = false; + + HierarchyContext *context = new HierarchyContext(); + context->object = dupli_object->ob; + context->duplicator = duplicator; + context->weak_export = false; + context->export_path = ""; + context->original_export_path = ""; + + /* If the dupli-object's parent is also instanced by this object, use that as the + * export parent. Otherwise use the dupli-parent as export parent. */ + Object *parent = dupli_object->ob->parent; + if (parent != nullptr && dupli_set.find(parent) != dupli_set.end()) { + // The parent object is part of the duplicated collection. + context->export_parent = parent; + graph_index = std::make_pair(parent, duplicator); + } + else { + /* The parent object is NOT part of the duplicated collection. This means that the world + * transform of this dupliobject can be influenced by objects that are not part of its + * export graph. */ + animation_check_include_parent = true; + context->export_parent = duplicator; + graph_index = std::make_pair(duplicator, nullptr); + } + + context->animation_check_include_parent = animation_check_include_parent; + copy_m4_m4(context->matrix_world, dupli_object->mat); + + // Construct export name for the dupli-instance. + std::stringstream suffix_stream; + suffix_stream << std::hex; + for (int i = 0; i < MAX_DUPLI_RECUR && dupli_object->persistent_id[i] != INT_MAX; i++) { + suffix_stream << "-" << dupli_object->persistent_id[i]; + } + context->export_name = make_valid_name(get_object_name(context->object) + suffix_stream.str()); + + export_graph_[graph_index].insert(context); +} + +AbstractHierarchyIterator::ExportChildren &AbstractHierarchyIterator::graph_children( + const HierarchyContext *context) +{ + if (context == nullptr) { + return export_graph_[std::make_pair(nullptr, nullptr)]; + } + + return export_graph_[std::make_pair(context->object, context->duplicator)]; +} + +void AbstractHierarchyIterator::determine_export_paths(const HierarchyContext *parent_context) +{ + const std::string &parent_export_path = parent_context ? parent_context->export_path : ""; + + for (HierarchyContext *context : graph_children(parent_context)) { + context->export_path = path_concatenate(parent_export_path, context->export_name); + + if (context->duplicator == nullptr) { + /* This is an original (i.e. non-instanced) object, so we should keep track of where it was + * exported to, just in case it gets instanced somewhere. */ + ID *source_ob = &context->object->id; + duplisource_export_path_[source_ob] = context->export_path; + + if (context->object->data != nullptr) { + ID *object_data = static_cast<ID *>(context->object->data); + ID *source_data = object_data; + duplisource_export_path_[source_data] = get_object_data_path(context); + } + } + + determine_export_paths(context); + } +} + +void AbstractHierarchyIterator::determine_duplication_references( + const HierarchyContext *parent_context, std::string indent) +{ + ExportChildren children = graph_children(parent_context); + + for (HierarchyContext *context : children) { + if (context->duplicator != nullptr) { + ID *source_id = &context->object->id; + const ExportPathMap::const_iterator &it = duplisource_export_path_.find(source_id); + + if (it == duplisource_export_path_.end()) { + // The original was not found, so mark this instance as "the original". + context->mark_as_not_instanced(); + duplisource_export_path_[source_id] = context->export_path; + } + else { + context->mark_as_instance_of(it->second); + } + + if (context->object->data) { + ID *source_data_id = (ID *)context->object->data; + const ExportPathMap::const_iterator &it = duplisource_export_path_.find(source_data_id); + + if (it == duplisource_export_path_.end()) { + // The original was not found, so mark this instance as "original". + std::string data_path = get_object_data_path(context); + context->mark_as_not_instanced(); + duplisource_export_path_[source_id] = context->export_path; + duplisource_export_path_[source_data_id] = data_path; + } + } + } + + determine_duplication_references(context, indent + " "); + } +} + +void AbstractHierarchyIterator::make_writers(const HierarchyContext *parent_context) +{ + AbstractHierarchyWriter *transform_writer = nullptr; + float parent_matrix_inv_world[4][4]; + + if (parent_context) { + invert_m4_m4(parent_matrix_inv_world, parent_context->matrix_world); + } + else { + unit_m4(parent_matrix_inv_world); + } + + for (HierarchyContext *context : graph_children(parent_context)) { + copy_m4_m4(context->parent_matrix_inv_world, parent_matrix_inv_world); + + // Get or create the transform writer. + transform_writer = ensure_writer(context, &AbstractHierarchyIterator::create_transform_writer); + if (transform_writer == nullptr) { + // Unable to export, so there is nothing to attach any children to; just abort this entire + // branch of the export hierarchy. + return; + } + + BLI_assert(DEG_is_evaluated_object(context->object)); + /* XXX This can lead to too many XForms being written. For example, a camera writer can refuse + * to write an orthographic camera. By the time that this is known, the XForm has already been + * written. */ + transform_writer->write(*context); + + if (!context->weak_export) { + make_writers_particle_systems(context); + make_writer_object_data(context); + } + + // Recurse into this object's children. + make_writers(context); + } + + // TODO(Sybren): iterate over all unused writers and call unused_during_iteration() or something. +} + +void AbstractHierarchyIterator::make_writer_object_data(const HierarchyContext *context) +{ + if (context->object->data == nullptr) { + return; + } + + HierarchyContext data_context = *context; + data_context.export_path = get_object_data_path(context); + + /* data_context.original_export_path is just a copy from the context. It points to the object, + * but needs to point to the object data. */ + if (data_context.is_instance()) { + ID *object_data = static_cast<ID *>(context->object->data); + data_context.original_export_path = duplisource_export_path_[object_data]; + + /* If the object is marked as an instance, so should the object data. */ + BLI_assert(data_context.is_instance()); + } + + AbstractHierarchyWriter *data_writer; + data_writer = ensure_writer(&data_context, &AbstractHierarchyIterator::create_data_writer); + if (data_writer == nullptr) { + return; + } + + data_writer->write(data_context); +} + +void AbstractHierarchyIterator::make_writers_particle_systems( + const HierarchyContext *transform_context) +{ + Object *object = transform_context->object; + ParticleSystem *psys = static_cast<ParticleSystem *>(object->particlesystem.first); + for (; psys; psys = psys->next) { + if (!psys_check_enabled(object, psys, true)) { + continue; + } + + HierarchyContext hair_context = *transform_context; + hair_context.export_path = path_concatenate(transform_context->export_path, + get_id_name(&psys->part->id)); + hair_context.particle_system = psys; + + AbstractHierarchyWriter *writer = nullptr; + switch (psys->part->type) { + case PART_HAIR: + writer = ensure_writer(&hair_context, &AbstractHierarchyIterator::create_hair_writer); + break; + case PART_EMITTER: + writer = ensure_writer(&hair_context, &AbstractHierarchyIterator::create_particle_writer); + break; + } + + if (writer != nullptr) { + writer->write(hair_context); + } + } +} + +std::string AbstractHierarchyIterator::get_object_name(const Object *object) const +{ + return get_id_name(&object->id); +} + +std::string AbstractHierarchyIterator::get_object_data_name(const Object *object) const +{ + ID *object_data = static_cast<ID *>(object->data); + return get_id_name(object_data); +} + +AbstractHierarchyWriter *AbstractHierarchyIterator::get_writer(const std::string &export_path) +{ + WriterMap::iterator it = writers_.find(export_path); + + if (it == writers_.end()) { + return nullptr; + } + return it->second; +} + +AbstractHierarchyWriter *AbstractHierarchyIterator::ensure_writer( + HierarchyContext *context, AbstractHierarchyIterator::create_writer_func create_func) +{ + AbstractHierarchyWriter *writer = get_writer(context->export_path); + if (writer != nullptr) { + return writer; + } + + writer = (this->*create_func)(context); + if (writer == nullptr) { + return nullptr; + } + + writers_[context->export_path] = writer; + + return writer; +} + +std::string AbstractHierarchyIterator::path_concatenate(const std::string &parent_path, + const std::string &child_path) const +{ + return parent_path + "/" + child_path; +} + +bool AbstractHierarchyIterator::mark_as_weak_export(const Object * /*object*/) const +{ + return false; +} +bool AbstractHierarchyIterator::should_visit_dupli_object(const DupliObject *dupli_object) const +{ + // Removing dupli_object->no_draw hides things like custom bone shapes. + return !dupli_object->no_draw; +} + +} // namespace USD diff --git a/source/blender/io/usd/intern/abstract_hierarchy_iterator.h b/source/blender/io/usd/intern/abstract_hierarchy_iterator.h new file mode 100644 index 00000000000..8bca2ddd447 --- /dev/null +++ b/source/blender/io/usd/intern/abstract_hierarchy_iterator.h @@ -0,0 +1,251 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ + +/* + * This file contains the AbstractHierarchyIterator. It is intended for exporters for file + * formats that concern an entire hierarchy of objects (rather than, for example, an OBJ file that + * contains only a single mesh). Examples are Universal Scene Description (USD) and Alembic. + * AbstractHierarchyIterator is intended to be subclassed to support concrete file formats. + * + * The AbstractHierarchyIterator makes a distinction between the actual object hierarchy and the + * export hierarchy. The former is the parent/child structure in Blender, which can have multiple + * parent-like objects. For example, a duplicated object can have both a duplicator and a parent, + * both determining the final transform. The export hierarchy is the hierarchy as written to the + * file, and every object has only one export-parent. + * + * Currently the AbstractHierarchyIterator does not make any decisions about *what* to export. + * Selections like "selected only" or "no hair systems" are left to concrete subclasses. + */ + +#ifndef __ABSTRACT_HIERARCHY_ITERATOR_H__ +#define __ABSTRACT_HIERARCHY_ITERATOR_H__ + +#include <map> +#include <string> +#include <set> + +struct Base; +struct Depsgraph; +struct DupliObject; +struct ID; +struct Object; +struct ParticleSystem; +struct ViewLayer; + +namespace USD { + +class AbstractHierarchyWriter; + +/* HierarchyContext structs are created by the AbstractHierarchyIterator. Each HierarchyContext + * struct contains everything necessary to export a single object to a file. */ +struct HierarchyContext { + /*********** Determined during hierarchy iteration: ***************/ + Object *object; + Object *export_parent; + Object *duplicator; + float matrix_world[4][4]; + std::string export_name; + + /* When weak_export=true, the object will be exported only as transform, and only if is an + * ancestor of an object with weak_export=false. + * + * In other words: when weak_export=true but this object has no children, or all decendants also + * have weak_export=true, this object (and by recursive reasoning all its decendants) will be + * excluded from the export. + * + * The export hierarchy is kept as close to the the hierarchy in Blender as possible. As such, an + * object that serves as a parent for another object, but which should NOT be exported itself, is + * exported only as transform (i.e. as empty). This happens with objects that are part of a + * holdout collection (which prevents them from being exported) but also parent of an exported + * object. */ + bool weak_export; + + /* When true, this object should check its parents for animation data when determining whether + * it's animated. This is necessary when a parent object in Blender is not part of the export. */ + bool animation_check_include_parent; + + /*********** Determined during writer creation: ***************/ + float parent_matrix_inv_world[4][4]; // Inverse of the parent's world matrix. + std::string export_path; // Hierarchical path, such as "/grandparent/parent/objectname". + ParticleSystem *particle_system; // Only set for particle/hair writers. + + /* Hierarchical path of the object this object is duplicating; only set when this object should + * be stored as a reference to its original. It can happen that the original is not part of the + * exported objects, in which case this string is empty even though 'duplicator' is set. */ + std::string original_export_path; + + bool operator<(const HierarchyContext &other) const; + + /* Return a HierarchyContext representing the root of the export hierarchy. */ + static const HierarchyContext *root(); + + /* For handling instanced collections, instances created by particles, etc. */ + bool is_instance() const; + void mark_as_instance_of(const std::string &reference_export_path); + void mark_as_not_instanced(); +}; + +/* Abstract writer for objects. Create concrete subclasses to write to USD, Alembic, etc. + * + * Instantiated by the AbstractHierarchyIterator on the first frame an object exists. Generally + * that's the first frame to be exported, but can be later, for example when objects are + * instantiated by particles. The AbstractHierarchyWriter::write() function is called on every + * frame the object exists in the dependency graph and should be exported. + */ +class AbstractHierarchyWriter { + public: + virtual ~AbstractHierarchyWriter(); + virtual void write(HierarchyContext &context) = 0; + // TODO(Sybren): add function like absent() that's called when a writer was previously created, + // but wasn't used while exporting the current frame (for example, a particle-instanced mesh of + // which the particle is no longer alive). +}; + +/* AbstractHierarchyIterator iterates over objects in a dependency graph, and constructs export + * writers. These writers are then called to perform the actual writing to a USD or Alembic file. + * + * Dealing with file- and scene-level data (for example, creating a USD scene, setting the frame + * rate, etc.) is not part of the AbstractHierarchyIterator class structure, and should be done + * in separate code. + */ +class AbstractHierarchyIterator { + public: + /* Mapping from export path to writer. */ + typedef std::map<std::string, AbstractHierarchyWriter *> WriterMap; + /* Pair of a (potentially duplicated) object and its duplicator (or nullptr). + * This is typically used to store a pair of HierarchyContext::object and + * HierarchyContext::duplicator. */ + typedef std::pair<Object *, Object *> DupliAndDuplicator; + /* All the children of some object, as per the export hierarchy. */ + typedef std::set<HierarchyContext *> ExportChildren; + /* Mapping from an object and its duplicator to the object's export-children. */ + typedef std::map<DupliAndDuplicator, ExportChildren> ExportGraph; + /* Mapping from ID to its export path. This is used for instancing; given an + * instanced datablock, the export path of the original can be looked up. */ + typedef std::map<ID *, std::string> ExportPathMap; + + protected: + ExportGraph export_graph_; + ExportPathMap duplisource_export_path_; + Depsgraph *depsgraph_; + WriterMap writers_; + + public: + explicit AbstractHierarchyIterator(Depsgraph *depsgraph); + virtual ~AbstractHierarchyIterator(); + + /* Iterate over the depsgraph, create writers, and tell the writers to write. + * Main entry point for the AbstractHierarchyIterator, must be called for every to-be-exported + * frame. */ + void iterate_and_write(); + + /* Release all writers. Call after all frames have been exported. */ + void release_writers(); + + /* Convert the given name to something that is valid for the exported file format. + * This base implementation is a no-op; override in a concrete subclass. */ + virtual std::string make_valid_name(const std::string &name) const; + + /* Return the name of this ID datablock that is valid for the exported file format. Overriding is + * only necessary if make_valid_name(id->name+2) is not suitable for the exported file format. + * NULL-safe: when `id == nullptr` this returns an empty string. */ + virtual std::string get_id_name(const ID *id) const; + + /* Given a HierarchyContext of some Object *, return an export path that is valid for its + * object->data. Overriding is necessary when the exported format does NOT expect the object's + * data to be a child of the object. */ + virtual std::string get_object_data_path(const HierarchyContext *context) const; + + private: + void debug_print_export_graph(const ExportGraph &graph) const; + + void export_graph_construct(); + void connect_loose_objects(); + void export_graph_prune(); + void export_graph_clear(); + + void visit_object(Object *object, Object *export_parent, bool weak_export); + void visit_dupli_object(DupliObject *dupli_object, + Object *duplicator, + const std::set<Object *> &dupli_set); + + ExportChildren &graph_children(const HierarchyContext *parent_context); + + void determine_export_paths(const HierarchyContext *parent_context); + void determine_duplication_references(const HierarchyContext *parent_context, + std::string indent); + + void make_writers(const HierarchyContext *parent_context); + void make_writer_object_data(const HierarchyContext *context); + void make_writers_particle_systems(const HierarchyContext *context); + + /* Convenience wrappers around get_id_name(). */ + std::string get_object_name(const Object *object) const; + std::string get_object_data_name(const Object *object) const; + + AbstractHierarchyWriter *get_writer(const std::string &export_path); + + typedef AbstractHierarchyWriter *(AbstractHierarchyIterator::*create_writer_func)( + const HierarchyContext *); + /* Ensure that a writer exists; if it doesn't, call create_func(context). The create_func + * function should be one of the create_XXXX_writer(context) functions declared below. */ + AbstractHierarchyWriter *ensure_writer(HierarchyContext *context, + create_writer_func create_func); + + protected: + /* Construct a valid path for the export file format. This class concatenates by using '/' as a + * path separator, which is valid for both Alembic and USD. */ + virtual std::string path_concatenate(const std::string &parent_path, + const std::string &child_path) const; + + /* Return whether this object should be marked as 'weak export' or not. + * + * When this returns false, writers for the transform and data are created, + * and dupli-objects dupli-object generated from this object will be passed to + * should_visit_dupli_object(). + * + * When this returns true, only a transform writer is created and marked as + * 'weak export'. In this case, the transform writer will be removed before + * exporting starts, unless a decendant of this object is to be exported. + * Dupli-object generated from this object will also be skipped. + * + * See HierarchyContext::weak_export. + */ + virtual bool mark_as_weak_export(const Object *object) const; + + virtual bool should_visit_dupli_object(const DupliObject *dupli_object) const; + + /* These functions should create an AbstractHierarchyWriter subclass instance, or return + * nullptr if the object or its data should not be exported. Returning a nullptr for + * data/hair/particle will NOT prevent the transform to be written. + * + * The returned writer is owned by the AbstractHierarchyWriter, and should be freed in + * delete_object_writer(). */ + virtual AbstractHierarchyWriter *create_transform_writer(const HierarchyContext *context) = 0; + virtual AbstractHierarchyWriter *create_data_writer(const HierarchyContext *context) = 0; + virtual AbstractHierarchyWriter *create_hair_writer(const HierarchyContext *context) = 0; + virtual AbstractHierarchyWriter *create_particle_writer(const HierarchyContext *context) = 0; + + /* Called by release_writers() to free what the create_XXX_writer() functions allocated. */ + virtual void delete_object_writer(AbstractHierarchyWriter *writer) = 0; +}; + +} // namespace USD + +#endif /* __ABSTRACT_HIERARCHY_ITERATOR_H__ */ diff --git a/source/blender/io/usd/intern/usd_capi.cc b/source/blender/io/usd/intern/usd_capi.cc new file mode 100644 index 00000000000..83e11cd7bf3 --- /dev/null +++ b/source/blender/io/usd/intern/usd_capi.cc @@ -0,0 +1,233 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ + +#include "usd.h" +#include "usd_hierarchy_iterator.h" + +#include <pxr/pxr.h> +#include <pxr/usd/usd/stage.h> +#include <pxr/usd/usdGeom/tokens.h> + +#include "MEM_guardedalloc.h" + +extern "C" { +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" +#include "DEG_depsgraph_query.h" + +#include "DNA_scene_types.h" + +#include "BKE_blender_version.h" +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_scene.h" + +#include "BLI_fileops.h" +#include "BLI_path_util.h" +#include "BLI_string.h" + +#include "WM_api.h" +#include "WM_types.h" +} + +namespace USD { + +struct ExportJobData { + ViewLayer *view_layer; + Main *bmain; + Depsgraph *depsgraph; + wmWindowManager *wm; + + char filename[FILE_MAX]; + USDExportParams params; + + short *stop; + short *do_update; + float *progress; + + bool was_canceled; + bool export_ok; +}; + +static void export_startjob(void *customdata, short *stop, short *do_update, float *progress) +{ + ExportJobData *data = static_cast<ExportJobData *>(customdata); + + data->stop = stop; + data->do_update = do_update; + data->progress = progress; + data->was_canceled = false; + + G.is_rendering = true; + WM_set_locked_interface(data->wm, true); + G.is_break = false; + + // Construct the depsgraph for exporting. + Scene *scene = DEG_get_input_scene(data->depsgraph); + ViewLayer *view_layer = DEG_get_input_view_layer(data->depsgraph); + DEG_graph_build_from_view_layer(data->depsgraph, data->bmain, scene, view_layer); + BKE_scene_graph_update_tagged(data->depsgraph, data->bmain); + + *progress = 0.0f; + *do_update = true; + + // For restoring the current frame after exporting animation is done. + const int orig_frame = CFRA; + + pxr::UsdStageRefPtr usd_stage = pxr::UsdStage::CreateNew(data->filename); + if (!usd_stage) { + /* This happens when the USD JSON files cannot be found. When that happens, + * the USD library doesn't know it has the functionality to write USDA and + * USDC files, and creating a new UsdStage fails. */ + WM_reportf( + RPT_ERROR, "USD Export: unable to find suitable USD plugin to write %s", data->filename); + data->export_ok = false; + return; + } + + usd_stage->SetMetadata(pxr::UsdGeomTokens->upAxis, pxr::VtValue(pxr::UsdGeomTokens->z)); + usd_stage->SetMetadata(pxr::UsdGeomTokens->metersPerUnit, + pxr::VtValue(scene->unit.scale_length)); + usd_stage->GetRootLayer()->SetDocumentation(std::string("Blender ") + versionstr); + + // Set up the stage for animated data. + if (data->params.export_animation) { + usd_stage->SetTimeCodesPerSecond(FPS); + usd_stage->SetStartTimeCode(scene->r.sfra); + usd_stage->SetEndTimeCode(scene->r.efra); + } + + USDHierarchyIterator iter(data->depsgraph, usd_stage, data->params); + + if (data->params.export_animation) { + // Writing the animated frames is not 100% of the work, but it's our best guess. + float progress_per_frame = 1.0f / std::max(1, (scene->r.efra - scene->r.sfra + 1)); + + for (float frame = scene->r.sfra; frame <= scene->r.efra; frame++) { + if (G.is_break || (stop != nullptr && *stop)) { + break; + } + + // Update the scene for the next frame to render. + scene->r.cfra = static_cast<int>(frame); + scene->r.subframe = frame - scene->r.cfra; + BKE_scene_graph_update_for_newframe(data->depsgraph, data->bmain); + + iter.set_export_frame(frame); + iter.iterate_and_write(); + + *progress += progress_per_frame; + *do_update = true; + } + } + else { + // If we're not animating, a single iteration over all objects is enough. + iter.iterate_and_write(); + } + + iter.release_writers(); + usd_stage->GetRootLayer()->Save(); + + // Finish up by going back to the keyframe that was current before we started. + if (CFRA != orig_frame) { + CFRA = orig_frame; + BKE_scene_graph_update_for_newframe(data->depsgraph, data->bmain); + } + + data->export_ok = !data->was_canceled; + + *progress = 1.0f; + *do_update = true; +} + +static void export_endjob(void *customdata) +{ + ExportJobData *data = static_cast<ExportJobData *>(customdata); + + DEG_graph_free(data->depsgraph); + + if (data->was_canceled && BLI_exists(data->filename)) { + BLI_delete(data->filename, false, false); + } + + G.is_rendering = false; + WM_set_locked_interface(data->wm, false); +} + +} // namespace USD + +bool USD_export(bContext *C, + const char *filepath, + const USDExportParams *params, + bool as_background_job) +{ + ViewLayer *view_layer = CTX_data_view_layer(C); + Scene *scene = CTX_data_scene(C); + + USD::ExportJobData *job = static_cast<USD::ExportJobData *>( + MEM_mallocN(sizeof(USD::ExportJobData), "ExportJobData")); + + job->bmain = CTX_data_main(C); + job->wm = CTX_wm_manager(C); + job->export_ok = false; + BLI_strncpy(job->filename, filepath, sizeof(job->filename)); + + job->depsgraph = DEG_graph_new(job->bmain, scene, view_layer, params->evaluation_mode); + job->params = *params; + + bool export_ok = false; + if (as_background_job) { + wmJob *wm_job = WM_jobs_get( + job->wm, CTX_wm_window(C), scene, "USD Export", WM_JOB_PROGRESS, WM_JOB_TYPE_ALEMBIC); + + /* setup job */ + WM_jobs_customdata_set(wm_job, job, MEM_freeN); + WM_jobs_timer(wm_job, 0.1, NC_SCENE | ND_FRAME, NC_SCENE | ND_FRAME); + WM_jobs_callbacks(wm_job, USD::export_startjob, NULL, NULL, USD::export_endjob); + + WM_jobs_start(CTX_wm_manager(C), wm_job); + } + else { + /* Fake a job context, so that we don't need NULL pointer checks while exporting. */ + short stop = 0, do_update = 0; + float progress = 0.f; + + USD::export_startjob(job, &stop, &do_update, &progress); + USD::export_endjob(job); + export_ok = job->export_ok; + + MEM_freeN(job); + } + + return export_ok; +} + +int USD_get_version(void) +{ + /* USD 19.11 defines: + * + * #define PXR_MAJOR_VERSION 0 + * #define PXR_MINOR_VERSION 19 + * #define PXR_PATCH_VERSION 11 + * #define PXR_VERSION 1911 + * + * So the major version is implicit/invisible in the public version number. + */ + return PXR_VERSION; +} diff --git a/source/blender/io/usd/intern/usd_exporter_context.h b/source/blender/io/usd/intern/usd_exporter_context.h new file mode 100644 index 00000000000..4ae415b3d34 --- /dev/null +++ b/source/blender/io/usd/intern/usd_exporter_context.h @@ -0,0 +1,44 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ +#ifndef __USD_EXPORTER_CONTEXT_H__ +#define __USD_EXPORTER_CONTEXT_H__ + +#include "usd.h" + +#include <pxr/usd/sdf/path.h> +#include <pxr/usd/usd/common.h> + +struct Depsgraph; +struct Object; + +namespace USD { + +class USDHierarchyIterator; + +struct USDExporterContext { + Depsgraph *depsgraph; + const pxr::UsdStageRefPtr stage; + const pxr::SdfPath usd_path; + const USDHierarchyIterator *hierarchy_iterator; + const USDExportParams &export_params; +}; + +} // namespace USD + +#endif /* __USD_EXPORTER_CONTEXT_H__ */ diff --git a/source/blender/io/usd/intern/usd_hierarchy_iterator.cc b/source/blender/io/usd/intern/usd_hierarchy_iterator.cc new file mode 100644 index 00000000000..fd888f39adc --- /dev/null +++ b/source/blender/io/usd/intern/usd_hierarchy_iterator.cc @@ -0,0 +1,150 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ +#include "usd.h" + +#include "usd_hierarchy_iterator.h" +#include "usd_writer_abstract.h" +#include "usd_writer_camera.h" +#include "usd_writer_hair.h" +#include "usd_writer_light.h" +#include "usd_writer_mesh.h" +#include "usd_writer_metaball.h" +#include "usd_writer_transform.h" + +#include <string> + +#include <pxr/base/tf/stringUtils.h> + +extern "C" { +#include "BKE_anim.h" + +#include "BLI_assert.h" + +#include "DEG_depsgraph_query.h" + +#include "DNA_ID.h" +#include "DNA_layer_types.h" +#include "DNA_object_types.h" +} + +namespace USD { + +USDHierarchyIterator::USDHierarchyIterator(Depsgraph *depsgraph, + pxr::UsdStageRefPtr stage, + const USDExportParams ¶ms) + : AbstractHierarchyIterator(depsgraph), stage_(stage), params_(params) +{ +} + +bool USDHierarchyIterator::mark_as_weak_export(const Object *object) const +{ + if (params_.selected_objects_only && (object->base_flag & BASE_SELECTED) == 0) { + return true; + } + return false; +} + +void USDHierarchyIterator::delete_object_writer(AbstractHierarchyWriter *writer) +{ + delete static_cast<USDAbstractWriter *>(writer); +} + +std::string USDHierarchyIterator::make_valid_name(const std::string &name) const +{ + return pxr::TfMakeValidIdentifier(name); +} + +void USDHierarchyIterator::set_export_frame(float frame_nr) +{ + // The USD stage is already set up to have FPS timecodes per frame. + export_time_ = pxr::UsdTimeCode(frame_nr); +} + +const pxr::UsdTimeCode &USDHierarchyIterator::get_export_time_code() const +{ + return export_time_; +} + +USDExporterContext USDHierarchyIterator::create_usd_export_context(const HierarchyContext *context) +{ + return USDExporterContext{depsgraph_, stage_, pxr::SdfPath(context->export_path), this, params_}; +} + +AbstractHierarchyWriter *USDHierarchyIterator::create_transform_writer( + const HierarchyContext *context) +{ + return new USDTransformWriter(create_usd_export_context(context)); +} + +AbstractHierarchyWriter *USDHierarchyIterator::create_data_writer(const HierarchyContext *context) +{ + USDExporterContext usd_export_context = create_usd_export_context(context); + USDAbstractWriter *data_writer = nullptr; + + switch (context->object->type) { + case OB_MESH: + data_writer = new USDMeshWriter(usd_export_context); + break; + case OB_CAMERA: + data_writer = new USDCameraWriter(usd_export_context); + break; + case OB_LAMP: + data_writer = new USDLightWriter(usd_export_context); + break; + case OB_MBALL: + data_writer = new USDMetaballWriter(usd_export_context); + break; + + case OB_EMPTY: + case OB_CURVE: + case OB_SURF: + case OB_FONT: + case OB_SPEAKER: + case OB_LIGHTPROBE: + case OB_LATTICE: + case OB_ARMATURE: + case OB_GPENCIL: + return nullptr; + case OB_TYPE_MAX: + BLI_assert(!"OB_TYPE_MAX should not be used"); + return nullptr; + } + + if (!data_writer->is_supported(context)) { + delete data_writer; + return nullptr; + } + + return data_writer; +} + +AbstractHierarchyWriter *USDHierarchyIterator::create_hair_writer(const HierarchyContext *context) +{ + if (!params_.export_hair) { + return nullptr; + } + return new USDHairWriter(create_usd_export_context(context)); +} + +AbstractHierarchyWriter *USDHierarchyIterator::create_particle_writer(const HierarchyContext *) +{ + return nullptr; +} + +} // namespace USD diff --git a/source/blender/io/usd/intern/usd_hierarchy_iterator.h b/source/blender/io/usd/intern/usd_hierarchy_iterator.h new file mode 100644 index 00000000000..90c82c6e551 --- /dev/null +++ b/source/blender/io/usd/intern/usd_hierarchy_iterator.h @@ -0,0 +1,71 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ +#ifndef __USD_HIERARCHY_ITERATOR_H__ +#define __USD_HIERARCHY_ITERATOR_H__ + +#include "abstract_hierarchy_iterator.h" +#include "usd_exporter_context.h" +#include "usd.h" + +#include <string> + +#include <pxr/usd/usd/common.h> +#include <pxr/usd/usd/timeCode.h> + +struct Depsgraph; +struct ID; +struct Object; + +namespace USD { + +class USDHierarchyIterator : public AbstractHierarchyIterator { + private: + const pxr::UsdStageRefPtr stage_; + pxr::UsdTimeCode export_time_; + const USDExportParams ¶ms_; + + public: + USDHierarchyIterator(Depsgraph *depsgraph, + pxr::UsdStageRefPtr stage, + const USDExportParams ¶ms); + + void set_export_frame(float frame_nr); + const pxr::UsdTimeCode &get_export_time_code() const; + + virtual std::string make_valid_name(const std::string &name) const override; + + protected: + virtual bool mark_as_weak_export(const Object *object) const override; + + virtual AbstractHierarchyWriter *create_transform_writer( + const HierarchyContext *context) override; + virtual AbstractHierarchyWriter *create_data_writer(const HierarchyContext *context) override; + virtual AbstractHierarchyWriter *create_hair_writer(const HierarchyContext *context) override; + virtual AbstractHierarchyWriter *create_particle_writer( + const HierarchyContext *context) override; + + virtual void delete_object_writer(AbstractHierarchyWriter *writer) override; + + private: + USDExporterContext create_usd_export_context(const HierarchyContext *context); +}; + +} // namespace USD + +#endif /* __USD_HIERARCHY_ITERATOR_H__ */ diff --git a/source/blender/io/usd/intern/usd_writer_abstract.cc b/source/blender/io/usd/intern/usd_writer_abstract.cc new file mode 100644 index 00000000000..4d0b4364fb5 --- /dev/null +++ b/source/blender/io/usd/intern/usd_writer_abstract.cc @@ -0,0 +1,147 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ +#include "usd_writer_abstract.h" +#include "usd_hierarchy_iterator.h" + +#include <pxr/base/tf/stringUtils.h> + +extern "C" { +#include "BKE_animsys.h" +#include "BKE_key.h" + +#include "DNA_modifier_types.h" +} + +/* TfToken objects are not cheap to construct, so we do it once. */ +namespace usdtokens { +// Materials +static const pxr::TfToken diffuse_color("diffuseColor", pxr::TfToken::Immortal); +static const pxr::TfToken metallic("metallic", pxr::TfToken::Immortal); +static const pxr::TfToken preview_shader("previewShader", pxr::TfToken::Immortal); +static const pxr::TfToken preview_surface("UsdPreviewSurface", pxr::TfToken::Immortal); +static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal); +static const pxr::TfToken surface("surface", pxr::TfToken::Immortal); +} // namespace usdtokens + +namespace USD { + +USDAbstractWriter::USDAbstractWriter(const USDExporterContext &usd_export_context) + : usd_export_context_(usd_export_context), + usd_value_writer_(), + frame_has_been_written_(false), + is_animated_(false) +{ +} + +USDAbstractWriter::~USDAbstractWriter() +{ +} + +bool USDAbstractWriter::is_supported(const HierarchyContext * /*context*/) const +{ + return true; +} + +pxr::UsdTimeCode USDAbstractWriter::get_export_time_code() const +{ + if (is_animated_) { + return usd_export_context_.hierarchy_iterator->get_export_time_code(); + } + // By using the default timecode USD won't even write a single `timeSample` for non-animated + // data. Instead, it writes it as non-timesampled. + static pxr::UsdTimeCode default_timecode = pxr::UsdTimeCode::Default(); + return default_timecode; +} + +void USDAbstractWriter::write(HierarchyContext &context) +{ + if (!frame_has_been_written_) { + is_animated_ = usd_export_context_.export_params.export_animation && + check_is_animated(context); + } + else if (!is_animated_) { + /* A frame has already been written, and without animation one frame is enough. */ + return; + } + + do_write(context); + + frame_has_been_written_ = true; +} + +bool USDAbstractWriter::check_is_animated(const HierarchyContext &context) const +{ + const Object *object = context.object; + + if (BKE_animdata_id_is_animated(static_cast<ID *>(object->data))) { + return true; + } + if (BKE_key_from_object(object) != nullptr) { + return true; + } + + /* Test modifiers. */ + /* TODO(Sybren): replace this with a check on the depsgraph to properly check for dependency on + * time. */ + ModifierData *md = static_cast<ModifierData *>(object->modifiers.first); + while (md) { + if (md->type != eModifierType_Subsurf) { + return true; + } + md = md->next; + } + + return false; +} + +const pxr::SdfPath &USDAbstractWriter::usd_path() const +{ + return usd_export_context_.usd_path; +} + +pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(Material *material) +{ + static pxr::SdfPath material_library_path("/_materials"); + pxr::UsdStageRefPtr stage = usd_export_context_.stage; + + // Construct the material. + pxr::TfToken material_name(usd_export_context_.hierarchy_iterator->get_id_name(&material->id)); + pxr::SdfPath usd_path = material_library_path.AppendChild(material_name); + pxr::UsdShadeMaterial usd_material = pxr::UsdShadeMaterial::Get(stage, usd_path); + if (usd_material) { + return usd_material; + } + usd_material = pxr::UsdShadeMaterial::Define(stage, usd_path); + + // Construct the shader. + pxr::SdfPath shader_path = usd_path.AppendChild(usdtokens::preview_shader); + pxr::UsdShadeShader shader = pxr::UsdShadeShader::Define(stage, shader_path); + shader.CreateIdAttr(pxr::VtValue(usdtokens::preview_surface)); + shader.CreateInput(usdtokens::diffuse_color, pxr::SdfValueTypeNames->Color3f) + .Set(pxr::GfVec3f(material->r, material->g, material->b)); + shader.CreateInput(usdtokens::roughness, pxr::SdfValueTypeNames->Float).Set(material->roughness); + shader.CreateInput(usdtokens::metallic, pxr::SdfValueTypeNames->Float).Set(material->metallic); + + // Connect the shader and the material together. + usd_material.CreateSurfaceOutput().ConnectToSource(shader, usdtokens::surface); + + return usd_material; +} + +} // namespace USD diff --git a/source/blender/io/usd/intern/usd_writer_abstract.h b/source/blender/io/usd/intern/usd_writer_abstract.h new file mode 100644 index 00000000000..835d3a42c80 --- /dev/null +++ b/source/blender/io/usd/intern/usd_writer_abstract.h @@ -0,0 +1,77 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ +#ifndef __USD_WRITER_ABSTRACT_H__ +#define __USD_WRITER_ABSTRACT_H__ + +#include "usd_exporter_context.h" +#include "abstract_hierarchy_iterator.h" + +#include <pxr/usd/sdf/path.h> +#include <pxr/usd/usd/stage.h> +#include <pxr/usd/usdShade/material.h> +#include <pxr/usd/usdUtils/sparseValueWriter.h> + +#include <vector> + +extern "C" { +#include "DEG_depsgraph_query.h" +#include "DNA_material_types.h" +} + +struct Material; +struct Object; + +namespace USD { + +class USDAbstractWriter : public AbstractHierarchyWriter { + protected: + const USDExporterContext usd_export_context_; + pxr::UsdUtilsSparseValueWriter usd_value_writer_; + + bool frame_has_been_written_; + bool is_animated_; + + public: + USDAbstractWriter(const USDExporterContext &usd_export_context); + virtual ~USDAbstractWriter(); + + virtual void write(HierarchyContext &context) override; + + /* Returns true if the data to be written is actually supported. This would, for example, allow a + * hypothetical camera writer accept a perspective camera but reject an orthogonal one. + * + * Returning false from a transform writer will prevent the object and all its decendants from + * being exported. Returning false from a data writer (object data, hair, or particles) will + * only prevent that data from being written (and thus cause the object to be exported as an + * Empty). */ + virtual bool is_supported(const HierarchyContext *context) const; + + const pxr::SdfPath &usd_path() const; + + protected: + virtual void do_write(HierarchyContext &context) = 0; + virtual bool check_is_animated(const HierarchyContext &context) const; + pxr::UsdTimeCode get_export_time_code() const; + + pxr::UsdShadeMaterial ensure_usd_material(Material *material); +}; + +} // namespace USD + +#endif /* __USD_WRITER_ABSTRACT_H__ */ diff --git a/source/blender/io/usd/intern/usd_writer_camera.cc b/source/blender/io/usd/intern/usd_writer_camera.cc new file mode 100644 index 00000000000..9b85d69559c --- /dev/null +++ b/source/blender/io/usd/intern/usd_writer_camera.cc @@ -0,0 +1,111 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ +#include "usd_writer_camera.h" +#include "usd_hierarchy_iterator.h" + +#include <pxr/usd/usdGeom/camera.h> +#include <pxr/usd/usdGeom/tokens.h> + +extern "C" { +#include "BKE_camera.h" +#include "BLI_assert.h" + +#include "DNA_camera_types.h" +#include "DNA_scene_types.h" +} + +namespace USD { + +USDCameraWriter::USDCameraWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx) +{ +} + +bool USDCameraWriter::is_supported(const HierarchyContext *context) const +{ + Camera *camera = static_cast<Camera *>(context->object->data); + return camera->type == CAM_PERSP; +} + +static void camera_sensor_size_for_render(const Camera *camera, + const struct RenderData *rd, + float *r_sensor_x, + float *r_sensor_y) +{ + /* Compute the final image size in pixels. */ + float sizex = rd->xsch * rd->xasp; + float sizey = rd->ysch * rd->yasp; + + int sensor_fit = BKE_camera_sensor_fit(camera->sensor_fit, sizex, sizey); + + switch (sensor_fit) { + case CAMERA_SENSOR_FIT_HOR: + *r_sensor_x = camera->sensor_x; + *r_sensor_y = camera->sensor_x * sizey / sizex; + break; + case CAMERA_SENSOR_FIT_VERT: + *r_sensor_x = camera->sensor_y * sizex / sizey; + *r_sensor_y = camera->sensor_y; + break; + case CAMERA_SENSOR_FIT_AUTO: + BLI_assert(!"Camera fit should be either horizontal or vertical"); + break; + } +} + +void USDCameraWriter::do_write(HierarchyContext &context) +{ + pxr::UsdTimeCode timecode = get_export_time_code(); + pxr::UsdGeomCamera usd_camera = pxr::UsdGeomCamera::Define(usd_export_context_.stage, + usd_export_context_.usd_path); + + Camera *camera = static_cast<Camera *>(context.object->data); + Scene *scene = DEG_get_evaluated_scene(usd_export_context_.depsgraph); + + usd_camera.CreateProjectionAttr().Set(pxr::UsdGeomTokens->perspective); + + /* USD stores the focal length in "millimeters or tenths of world units", because at some point + * they decided world units might be centimeters. Quite confusing, as the USD Viewer shows the + * correct FoV when we write millimeters and not "tenths of world units". + */ + usd_camera.CreateFocalLengthAttr().Set(camera->lens, timecode); + + float aperture_x, aperture_y; + camera_sensor_size_for_render(camera, &scene->r, &aperture_x, &aperture_y); + + float film_aspect = aperture_x / aperture_y; + usd_camera.CreateHorizontalApertureAttr().Set(aperture_x, timecode); + usd_camera.CreateVerticalApertureAttr().Set(aperture_y, timecode); + usd_camera.CreateHorizontalApertureOffsetAttr().Set(aperture_x * camera->shiftx, timecode); + usd_camera.CreateVerticalApertureOffsetAttr().Set(aperture_y * camera->shifty * film_aspect, + timecode); + + usd_camera.CreateClippingRangeAttr().Set( + pxr::VtValue(pxr::GfVec2f(camera->clip_start, camera->clip_end)), timecode); + + // Write DoF-related attributes. + if (camera->dof.flag & CAM_DOF_ENABLED) { + usd_camera.CreateFStopAttr().Set(camera->dof.aperture_fstop, timecode); + + float focus_distance = scene->unit.scale_length * + BKE_camera_object_dof_distance(context.object); + usd_camera.CreateFocusDistanceAttr().Set(focus_distance, timecode); + } +} + +} // namespace USD diff --git a/source/blender/io/usd/intern/usd_writer_camera.h b/source/blender/io/usd/intern/usd_writer_camera.h new file mode 100644 index 00000000000..971264ef11e --- /dev/null +++ b/source/blender/io/usd/intern/usd_writer_camera.h @@ -0,0 +1,38 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ +#ifndef __USD_WRITER_CAMERA_H__ +#define __USD_WRITER_CAMERA_H__ + +#include "usd_writer_abstract.h" + +namespace USD { + +/* Writer for writing camera data to UsdGeomCamera. */ +class USDCameraWriter : public USDAbstractWriter { + public: + USDCameraWriter(const USDExporterContext &ctx); + + protected: + virtual bool is_supported(const HierarchyContext *context) const override; + virtual void do_write(HierarchyContext &context) override; +}; + +} // namespace USD + +#endif /* __USD_WRITER_CAMERA_H__ */ diff --git a/source/blender/io/usd/intern/usd_writer_hair.cc b/source/blender/io/usd/intern/usd_writer_hair.cc new file mode 100644 index 00000000000..9251425c0b8 --- /dev/null +++ b/source/blender/io/usd/intern/usd_writer_hair.cc @@ -0,0 +1,90 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ +#include "usd_writer_hair.h" +#include "usd_hierarchy_iterator.h" + +#include <pxr/usd/usdGeom/basisCurves.h> +#include <pxr/usd/usdGeom/tokens.h> + +extern "C" { +#include "BKE_particle.h" + +#include "DNA_particle_types.h" +} + +namespace USD { + +USDHairWriter::USDHairWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx) +{ +} + +void USDHairWriter::do_write(HierarchyContext &context) +{ + ParticleSystem *psys = context.particle_system; + ParticleCacheKey **cache = psys->pathcache; + if (cache == nullptr) { + return; + } + + pxr::UsdTimeCode timecode = get_export_time_code(); + pxr::UsdGeomBasisCurves curves = pxr::UsdGeomBasisCurves::Define(usd_export_context_.stage, + usd_export_context_.usd_path); + + // TODO(Sybren): deal with (psys->part->flag & PART_HAIR_BSPLINE) + curves.CreateBasisAttr(pxr::VtValue(pxr::UsdGeomTokens->bspline)); + curves.CreateTypeAttr(pxr::VtValue(pxr::UsdGeomTokens->cubic)); + + pxr::VtArray<pxr::GfVec3f> points; + pxr::VtIntArray curve_point_counts; + curve_point_counts.reserve(psys->totpart); + + ParticleCacheKey *strand; + for (int strand_index = 0; strand_index < psys->totpart; ++strand_index) { + strand = cache[strand_index]; + + int point_count = strand->segments + 1; + curve_point_counts.push_back(point_count); + + for (int point_index = 0; point_index < point_count; ++point_index, ++strand) { + points.push_back(pxr::GfVec3f(strand->co)); + } + } + + pxr::UsdAttribute attr_points = curves.CreatePointsAttr(pxr::VtValue(), true); + pxr::UsdAttribute attr_vertex_counts = curves.CreateCurveVertexCountsAttr(pxr::VtValue(), true); + if (!attr_points.HasValue()) { + attr_points.Set(points, pxr::UsdTimeCode::Default()); + attr_vertex_counts.Set(curve_point_counts, pxr::UsdTimeCode::Default()); + } + usd_value_writer_.SetAttribute(attr_points, pxr::VtValue(points), timecode); + usd_value_writer_.SetAttribute(attr_vertex_counts, pxr::VtValue(curve_point_counts), timecode); + + if (psys->totpart > 0) { + pxr::VtArray<pxr::GfVec3f> colors; + colors.push_back(pxr::GfVec3f(cache[0]->col)); + curves.CreateDisplayColorAttr(pxr::VtValue(colors)); + } +} + +bool USDHairWriter::check_is_animated(const HierarchyContext &) const +{ + return true; +} + +} // namespace USD diff --git a/source/blender/io/usd/intern/usd_writer_hair.h b/source/blender/io/usd/intern/usd_writer_hair.h new file mode 100644 index 00000000000..1e882fa1654 --- /dev/null +++ b/source/blender/io/usd/intern/usd_writer_hair.h @@ -0,0 +1,38 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ +#ifndef __USD_WRITER_HAIR_H__ +#define __USD_WRITER_HAIR_H__ + +#include "usd_writer_abstract.h" + +namespace USD { + +/* Writer for writing hair particle data as USD curves. */ +class USDHairWriter : public USDAbstractWriter { + public: + USDHairWriter(const USDExporterContext &ctx); + + protected: + virtual void do_write(HierarchyContext &context) override; + virtual bool check_is_animated(const HierarchyContext &context) const override; +}; + +} // namespace USD + +#endif /* __USD_WRITER_HAIR_H__ */ diff --git a/source/blender/io/usd/intern/usd_writer_light.cc b/source/blender/io/usd/intern/usd_writer_light.cc new file mode 100644 index 00000000000..e13e2c58a79 --- /dev/null +++ b/source/blender/io/usd/intern/usd_writer_light.cc @@ -0,0 +1,112 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ +#include "usd_writer_light.h" +#include "usd_hierarchy_iterator.h" + +#include <pxr/usd/usdLux/diskLight.h> +#include <pxr/usd/usdLux/distantLight.h> +#include <pxr/usd/usdLux/rectLight.h> +#include <pxr/usd/usdLux/sphereLight.h> + +extern "C" { +#include "BLI_assert.h" +#include "BLI_utildefines.h" + +#include "DNA_light_types.h" +#include "DNA_object_types.h" +} + +namespace USD { + +USDLightWriter::USDLightWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx) +{ +} + +bool USDLightWriter::is_supported(const HierarchyContext *context) const +{ + Light *light = static_cast<Light *>(context->object->data); + return ELEM(light->type, LA_AREA, LA_LOCAL, LA_SUN); +} + +void USDLightWriter::do_write(HierarchyContext &context) +{ + pxr::UsdStageRefPtr stage = usd_export_context_.stage; + const pxr::SdfPath &usd_path = usd_export_context_.usd_path; + pxr::UsdTimeCode timecode = get_export_time_code(); + + Light *light = static_cast<Light *>(context.object->data); + pxr::UsdLuxLight usd_light; + + switch (light->type) { + case LA_AREA: + switch (light->area_shape) { + case LA_AREA_DISK: + case LA_AREA_ELLIPSE: { /* An ellipse light will deteriorate into a disk light. */ + pxr::UsdLuxDiskLight disk_light = pxr::UsdLuxDiskLight::Define(stage, usd_path); + disk_light.CreateRadiusAttr().Set(light->area_size, timecode); + usd_light = disk_light; + break; + } + case LA_AREA_RECT: { + pxr::UsdLuxRectLight rect_light = pxr::UsdLuxRectLight::Define(stage, usd_path); + rect_light.CreateWidthAttr().Set(light->area_size, timecode); + rect_light.CreateHeightAttr().Set(light->area_sizey, timecode); + usd_light = rect_light; + break; + } + case LA_AREA_SQUARE: { + pxr::UsdLuxRectLight rect_light = pxr::UsdLuxRectLight::Define(stage, usd_path); + rect_light.CreateWidthAttr().Set(light->area_size, timecode); + rect_light.CreateHeightAttr().Set(light->area_size, timecode); + usd_light = rect_light; + break; + } + } + break; + case LA_LOCAL: { + pxr::UsdLuxSphereLight sphere_light = pxr::UsdLuxSphereLight::Define(stage, usd_path); + sphere_light.CreateRadiusAttr().Set(light->area_size, timecode); + usd_light = sphere_light; + break; + } + case LA_SUN: + usd_light = pxr::UsdLuxDistantLight::Define(stage, usd_path); + break; + default: + BLI_assert(!"is_supported() returned true for unsupported light type"); + } + + /* Scale factor to get to somewhat-similar illumination. Since the USDViewer had similar + * over-exposure as Blender Internal with the same values, this code applies the reverse of the + * versioning code in light_emission_unify(). */ + float usd_intensity; + if (light->type == LA_SUN) { + /* Untested, as the Hydra GL viewport of USDViewer doesn't support distant lights. */ + usd_intensity = light->energy; + } + else { + usd_intensity = light->energy / 100.f; + } + usd_light.CreateIntensityAttr().Set(usd_intensity, timecode); + + usd_light.CreateColorAttr().Set(pxr::GfVec3f(light->r, light->g, light->b), timecode); + usd_light.CreateSpecularAttr().Set(light->spec_fac, timecode); +} + +} // namespace USD diff --git a/source/blender/io/usd/intern/usd_writer_light.h b/source/blender/io/usd/intern/usd_writer_light.h new file mode 100644 index 00000000000..349c034b6bc --- /dev/null +++ b/source/blender/io/usd/intern/usd_writer_light.h @@ -0,0 +1,37 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ +#ifndef __USD_WRITER_LIGHT_H__ +#define __USD_WRITER_LIGHT_H__ + +#include "usd_writer_abstract.h" + +namespace USD { + +class USDLightWriter : public USDAbstractWriter { + public: + USDLightWriter(const USDExporterContext &ctx); + + protected: + virtual bool is_supported(const HierarchyContext *context) const override; + virtual void do_write(HierarchyContext &context) override; +}; + +} // namespace USD + +#endif /* __USD_WRITER_LIGHT_H__ */ diff --git a/source/blender/io/usd/intern/usd_writer_mesh.cc b/source/blender/io/usd/intern/usd_writer_mesh.cc new file mode 100644 index 00000000000..74005afaf31 --- /dev/null +++ b/source/blender/io/usd/intern/usd_writer_mesh.cc @@ -0,0 +1,489 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ +#include "usd_writer_mesh.h" +#include "usd_hierarchy_iterator.h" + +#include <pxr/usd/usdGeom/mesh.h> +#include <pxr/usd/usdShade/material.h> +#include <pxr/usd/usdShade/materialBindingAPI.h> + +extern "C" { +#include "BLI_assert.h" +#include "BLI_math_vector.h" + +#include "BKE_anim.h" +#include "BKE_customdata.h" +#include "BKE_lib_id.h" +#include "BKE_material.h" +#include "BKE_mesh.h" +#include "BKE_modifier.h" +#include "BKE_object.h" + +#include "DEG_depsgraph.h" + +#include "DNA_layer_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_fluidsim_types.h" +#include "DNA_particle_types.h" +} + +namespace USD { + +USDGenericMeshWriter::USDGenericMeshWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx) +{ +} + +bool USDGenericMeshWriter::is_supported(const HierarchyContext *context) const +{ + Object *object = context->object; + bool is_dupli = context->duplicator != nullptr; + int base_flag; + + if (is_dupli) { + /* Construct the object's base flags from its dupliparent, just like is done in + * deg_objects_dupli_iterator_next(). Without this, the visibility check below will fail. Doing + * this here, instead of a more suitable location in AbstractHierarchyIterator, prevents + * copying the Object for every dupli. */ + base_flag = object->base_flag; + object->base_flag = context->duplicator->base_flag | BASE_FROM_DUPLI; + } + + int visibility = BKE_object_visibility(object, + usd_export_context_.export_params.evaluation_mode); + + if (is_dupli) { + object->base_flag = base_flag; + } + + return (visibility & OB_VISIBLE_SELF) != 0; +} + +void USDGenericMeshWriter::do_write(HierarchyContext &context) +{ + Object *object_eval = context.object; + bool needsfree = false; + Mesh *mesh = get_export_mesh(object_eval, needsfree); + + if (mesh == NULL) { + return; + } + + try { + write_mesh(context, mesh); + + if (needsfree) { + free_export_mesh(mesh); + } + } + catch (...) { + if (needsfree) { + free_export_mesh(mesh); + } + throw; + } +} + +void USDGenericMeshWriter::free_export_mesh(Mesh *mesh) +{ + BKE_id_free(NULL, mesh); +} + +struct USDMeshData { + pxr::VtArray<pxr::GfVec3f> points; + pxr::VtIntArray face_vertex_counts; + pxr::VtIntArray face_indices; + std::map<short, pxr::VtIntArray> face_groups; + + /* The length of this array specifies the number of creases on the surface. Each element gives + * the number of (must be adjacent) vertices in each crease, whose indices are linearly laid out + * in the 'creaseIndices' attribute. Since each crease must be at least one edge long, each + * element of this array should be greater than one. */ + pxr::VtIntArray crease_lengths; + /* The indices of all vertices forming creased edges. The size of this array must be equal to the + * sum of all elements of the 'creaseLengths' attribute. */ + pxr::VtIntArray crease_vertex_indices; + /* The per-crease or per-edge sharpness for all creases (Usd.Mesh.SHARPNESS_INFINITE for a + * perfectly sharp crease). Since 'creaseLengths' encodes the number of vertices in each crease, + * the number of elements in this array will be either len(creaseLengths) or the sum over all X + * of (creaseLengths[X] - 1). Note that while the RI spec allows each crease to have either a + * single sharpness or a value per-edge, USD will encode either a single sharpness per crease on + * a mesh, or sharpnesses for all edges making up the creases on a mesh. */ + pxr::VtFloatArray crease_sharpnesses; +}; + +void USDGenericMeshWriter::write_uv_maps(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh) +{ + pxr::UsdTimeCode timecode = get_export_time_code(); + + const CustomData *ldata = &mesh->ldata; + for (int layer_idx = 0; layer_idx < ldata->totlayer; layer_idx++) { + const CustomDataLayer *layer = &ldata->layers[layer_idx]; + if (layer->type != CD_MLOOPUV) { + continue; + } + + /* UV coordinates are stored in a Primvar on the Mesh, and can be referenced from materials. + * The primvar name is the same as the UV Map name. This is to allow the standard name "st" + * for texture coordinates by naming the UV Map as such, without having to guess which UV Map + * is the "standard" one. */ + pxr::TfToken primvar_name(pxr::TfMakeValidIdentifier(layer->name)); + pxr::UsdGeomPrimvar uv_coords_primvar = usd_mesh.CreatePrimvar( + primvar_name, pxr::SdfValueTypeNames->TexCoord2fArray, pxr::UsdGeomTokens->faceVarying); + + MLoopUV *mloopuv = static_cast<MLoopUV *>(layer->data); + pxr::VtArray<pxr::GfVec2f> uv_coords; + for (int loop_idx = 0; loop_idx < mesh->totloop; loop_idx++) { + uv_coords.push_back(pxr::GfVec2f(mloopuv[loop_idx].uv)); + } + + if (!uv_coords_primvar.HasValue()) { + uv_coords_primvar.Set(uv_coords, pxr::UsdTimeCode::Default()); + } + const pxr::UsdAttribute &uv_coords_attr = uv_coords_primvar.GetAttr(); + usd_value_writer_.SetAttribute(uv_coords_attr, pxr::VtValue(uv_coords), timecode); + } +} + +void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh) +{ + pxr::UsdTimeCode timecode = get_export_time_code(); + pxr::UsdTimeCode defaultTime = pxr::UsdTimeCode::Default(); + pxr::UsdStageRefPtr stage = usd_export_context_.stage; + const pxr::SdfPath &usd_path = usd_export_context_.usd_path; + + pxr::UsdGeomMesh usd_mesh = pxr::UsdGeomMesh::Define(stage, usd_path); + USDMeshData usd_mesh_data; + get_geometry_data(mesh, usd_mesh_data); + + if (usd_export_context_.export_params.use_instancing && context.is_instance()) { + // This object data is instanced, just reference the original instead of writing a copy. + if (context.export_path == context.original_export_path) { + printf("USD ref error: export path is reference path: %s\n", context.export_path.c_str()); + BLI_assert(!"USD reference error"); + return; + } + pxr::SdfPath ref_path(context.original_export_path); + if (!usd_mesh.GetPrim().GetReferences().AddInternalReference(ref_path)) { + /* See this URL for a description fo why referencing may fail" + * https://graphics.pixar.com/usd/docs/api/class_usd_references.html#Usd_Failing_References + */ + printf("USD Export warning: unable to add reference from %s to %s, not instancing object\n", + context.export_path.c_str(), + context.original_export_path.c_str()); + return; + } + /* The material path will be of the form </_materials/{material name}>, which is outside the + subtree pointed to by ref_path. As a result, the referenced data is not allowed to point out + of its own subtree. It does work when we override the material with exactly the same path, + though.*/ + if (usd_export_context_.export_params.export_materials) { + assign_materials(context, usd_mesh, usd_mesh_data.face_groups); + } + return; + } + + pxr::UsdAttribute attr_points = usd_mesh.CreatePointsAttr(pxr::VtValue(), true); + pxr::UsdAttribute attr_face_vertex_counts = usd_mesh.CreateFaceVertexCountsAttr(pxr::VtValue(), + true); + pxr::UsdAttribute attr_face_vertex_indices = usd_mesh.CreateFaceVertexIndicesAttr(pxr::VtValue(), + true); + + if (!attr_points.HasValue()) { + // Provide the initial value as default. This makes USD write the value as constant if they + // don't change over time. + attr_points.Set(usd_mesh_data.points, defaultTime); + attr_face_vertex_counts.Set(usd_mesh_data.face_vertex_counts, defaultTime); + attr_face_vertex_indices.Set(usd_mesh_data.face_indices, defaultTime); + } + + usd_value_writer_.SetAttribute(attr_points, pxr::VtValue(usd_mesh_data.points), timecode); + usd_value_writer_.SetAttribute( + attr_face_vertex_counts, pxr::VtValue(usd_mesh_data.face_vertex_counts), timecode); + usd_value_writer_.SetAttribute( + attr_face_vertex_indices, pxr::VtValue(usd_mesh_data.face_indices), timecode); + + if (!usd_mesh_data.crease_lengths.empty()) { + pxr::UsdAttribute attr_crease_lengths = usd_mesh.CreateCreaseLengthsAttr(pxr::VtValue(), true); + pxr::UsdAttribute attr_crease_indices = usd_mesh.CreateCreaseIndicesAttr(pxr::VtValue(), true); + pxr::UsdAttribute attr_crease_sharpness = usd_mesh.CreateCreaseSharpnessesAttr(pxr::VtValue(), + true); + + if (!attr_crease_lengths.HasValue()) { + attr_crease_lengths.Set(usd_mesh_data.crease_lengths, defaultTime); + attr_crease_indices.Set(usd_mesh_data.crease_vertex_indices, defaultTime); + attr_crease_sharpness.Set(usd_mesh_data.crease_sharpnesses, defaultTime); + } + + usd_value_writer_.SetAttribute( + attr_crease_lengths, pxr::VtValue(usd_mesh_data.crease_lengths), timecode); + usd_value_writer_.SetAttribute( + attr_crease_indices, pxr::VtValue(usd_mesh_data.crease_vertex_indices), timecode); + usd_value_writer_.SetAttribute( + attr_crease_sharpness, pxr::VtValue(usd_mesh_data.crease_sharpnesses), timecode); + } + + if (usd_export_context_.export_params.export_uvmaps) { + write_uv_maps(mesh, usd_mesh); + } + if (usd_export_context_.export_params.export_normals) { + write_normals(mesh, usd_mesh); + } + write_surface_velocity(context.object, mesh, usd_mesh); + + // TODO(Sybren): figure out what happens when the face groups change. + if (frame_has_been_written_) { + return; + } + + usd_mesh.CreateSubdivisionSchemeAttr().Set(pxr::UsdGeomTokens->none); + + if (usd_export_context_.export_params.export_materials) { + assign_materials(context, usd_mesh, usd_mesh_data.face_groups); + } +} + +static void get_vertices(const Mesh *mesh, USDMeshData &usd_mesh_data) +{ + usd_mesh_data.points.reserve(mesh->totvert); + + const MVert *verts = mesh->mvert; + for (int i = 0; i < mesh->totvert; ++i) { + usd_mesh_data.points.push_back(pxr::GfVec3f(verts[i].co)); + } +} + +static void get_loops_polys(const Mesh *mesh, USDMeshData &usd_mesh_data) +{ + /* Only construct face groups (a.k.a. geometry subsets) when we need them for material + * assignments. */ + bool construct_face_groups = mesh->totcol > 1; + + usd_mesh_data.face_vertex_counts.reserve(mesh->totpoly); + usd_mesh_data.face_indices.reserve(mesh->totloop); + + MLoop *mloop = mesh->mloop; + MPoly *mpoly = mesh->mpoly; + for (int i = 0; i < mesh->totpoly; ++i, ++mpoly) { + MLoop *loop = mloop + mpoly->loopstart; + usd_mesh_data.face_vertex_counts.push_back(mpoly->totloop); + for (int j = 0; j < mpoly->totloop; ++j, ++loop) { + usd_mesh_data.face_indices.push_back(loop->v); + } + + if (construct_face_groups) { + usd_mesh_data.face_groups[mpoly->mat_nr].push_back(i); + } + } +} + +static void get_creases(const Mesh *mesh, USDMeshData &usd_mesh_data) +{ + const float factor = 1.0f / 255.0f; + + MEdge *edge = mesh->medge; + float sharpness; + for (int edge_idx = 0, totedge = mesh->totedge; edge_idx < totedge; ++edge_idx, ++edge) { + if (edge->crease == 0) { + continue; + } + + if (edge->crease == 255) { + sharpness = pxr::UsdGeomMesh::SHARPNESS_INFINITE; + } + else { + sharpness = static_cast<float>(edge->crease) * factor; + } + + usd_mesh_data.crease_vertex_indices.push_back(edge->v1); + usd_mesh_data.crease_vertex_indices.push_back(edge->v2); + usd_mesh_data.crease_lengths.push_back(2); + usd_mesh_data.crease_sharpnesses.push_back(sharpness); + } +} + +void USDGenericMeshWriter::get_geometry_data(const Mesh *mesh, USDMeshData &usd_mesh_data) +{ + get_vertices(mesh, usd_mesh_data); + get_loops_polys(mesh, usd_mesh_data); + get_creases(mesh, usd_mesh_data); +} + +void USDGenericMeshWriter::assign_materials(const HierarchyContext &context, + pxr::UsdGeomMesh usd_mesh, + const MaterialFaceGroups &usd_face_groups) +{ + if (context.object->totcol == 0) { + return; + } + + /* Binding a material to a geometry subset isn't supported by the Hydra GL viewport yet, + * which is why we always bind the first material to the entire mesh. See + * https://github.com/PixarAnimationStudios/USD/issues/542 for more info. */ + bool mesh_material_bound = false; + for (short mat_num = 0; mat_num < context.object->totcol; mat_num++) { + Material *material = BKE_object_material_get(context.object, mat_num + 1); + if (material == nullptr) { + continue; + } + + pxr::UsdShadeMaterial usd_material = ensure_usd_material(material); + usd_material.Bind(usd_mesh.GetPrim()); + + /* USD seems to support neither per-material nor per-face-group double-sidedness, so we just + * use the flag from the first non-empty material slot. */ + usd_mesh.CreateDoubleSidedAttr( + pxr::VtValue((material->blend_flag & MA_BL_CULL_BACKFACE) == 0)); + + mesh_material_bound = true; + break; + } + + if (!mesh_material_bound) { + /* Blender defaults to double-sided, but USD to single-sided. */ + usd_mesh.CreateDoubleSidedAttr(pxr::VtValue(true)); + } + + if (!mesh_material_bound || usd_face_groups.size() < 2) { + /* Either all material slots were empty or there is only one material in use. As geometry + * subsets are only written when actually used to assign a material, and the mesh already has + * the material assigned, there is no need to continue. */ + return; + } + + // Define a geometry subset per material. + for (const MaterialFaceGroups::value_type &face_group : usd_face_groups) { + short material_number = face_group.first; + const pxr::VtIntArray &face_indices = face_group.second; + + Material *material = BKE_object_material_get(context.object, material_number + 1); + if (material == nullptr) { + continue; + } + + pxr::UsdShadeMaterial usd_material = ensure_usd_material(material); + pxr::TfToken material_name = usd_material.GetPath().GetNameToken(); + + pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(usd_mesh); + pxr::UsdGeomSubset usd_face_subset = api.CreateMaterialBindSubset(material_name, face_indices); + usd_material.Bind(usd_face_subset.GetPrim()); + } +} + +void USDGenericMeshWriter::write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh) +{ + pxr::UsdTimeCode timecode = get_export_time_code(); + const float(*lnors)[3] = static_cast<float(*)[3]>(CustomData_get_layer(&mesh->ldata, CD_NORMAL)); + + pxr::VtVec3fArray loop_normals; + loop_normals.reserve(mesh->totloop); + + if (lnors != nullptr) { + /* Export custom loop normals. */ + for (int loop_idx = 0, totloop = mesh->totloop; loop_idx < totloop; ++loop_idx) { + loop_normals.push_back(pxr::GfVec3f(lnors[loop_idx])); + } + } + else { + /* Compute the loop normals based on the 'smooth' flag. */ + float normal[3]; + MPoly *mpoly = mesh->mpoly; + const MVert *mvert = mesh->mvert; + for (int poly_idx = 0, totpoly = mesh->totpoly; poly_idx < totpoly; ++poly_idx, ++mpoly) { + MLoop *mloop = mesh->mloop + mpoly->loopstart; + + if ((mpoly->flag & ME_SMOOTH) == 0) { + /* Flat shaded, use common normal for all verts. */ + BKE_mesh_calc_poly_normal(mpoly, mloop, mvert, normal); + pxr::GfVec3f pxr_normal(normal); + for (int loop_idx = 0; loop_idx < mpoly->totloop; ++loop_idx) { + loop_normals.push_back(pxr_normal); + } + } + else { + /* Smooth shaded, use individual vert normals. */ + for (int loop_idx = 0; loop_idx < mpoly->totloop; ++loop_idx, ++mloop) { + normal_short_to_float_v3(normal, mvert[mloop->v].no); + loop_normals.push_back(pxr::GfVec3f(normal)); + } + } + } + } + + pxr::UsdAttribute attr_normals = usd_mesh.CreateNormalsAttr(pxr::VtValue(), true); + if (!attr_normals.HasValue()) { + attr_normals.Set(loop_normals, pxr::UsdTimeCode::Default()); + } + usd_value_writer_.SetAttribute(attr_normals, pxr::VtValue(loop_normals), timecode); + usd_mesh.SetNormalsInterpolation(pxr::UsdGeomTokens->faceVarying); +} + +void USDGenericMeshWriter::write_surface_velocity(Object *object, + const Mesh *mesh, + pxr::UsdGeomMesh usd_mesh) +{ + /* Only velocities from the fluid simulation are exported. This is the most important case, + * though, as the baked mesh changes topology all the time, and thus computing the velocities + * at import time in a post-processing step is hard. */ + ModifierData *md = modifiers_findByType(object, eModifierType_Fluidsim); + if (md == nullptr) { + return; + } + + /* Check that the fluid sim modifier is enabled and has useful data. */ + const bool use_render = (DEG_get_mode(usd_export_context_.depsgraph) == DAG_EVAL_RENDER); + const ModifierMode required_mode = use_render ? eModifierMode_Render : eModifierMode_Realtime; + const Scene *scene = DEG_get_evaluated_scene(usd_export_context_.depsgraph); + if (!modifier_isEnabled(scene, md, required_mode)) { + return; + } + FluidsimModifierData *fsmd = reinterpret_cast<FluidsimModifierData *>(md); + if (!fsmd->fss || fsmd->fss->type != OB_FLUIDSIM_DOMAIN) { + return; + } + FluidsimSettings *fss = fsmd->fss; + if (!fss->meshVelocities) { + return; + } + + /* Export per-vertex velocity vectors. */ + pxr::VtVec3fArray usd_velocities; + usd_velocities.reserve(mesh->totvert); + + FluidVertexVelocity *mesh_velocities = fss->meshVelocities; + for (int vertex_idx = 0, totvert = mesh->totvert; vertex_idx < totvert; + ++vertex_idx, ++mesh_velocities) { + usd_velocities.push_back(pxr::GfVec3f(mesh_velocities->vel)); + } + + pxr::UsdTimeCode timecode = get_export_time_code(); + usd_mesh.CreateVelocitiesAttr().Set(usd_velocities, timecode); +} + +USDMeshWriter::USDMeshWriter(const USDExporterContext &ctx) : USDGenericMeshWriter(ctx) +{ +} + +Mesh *USDMeshWriter::get_export_mesh(Object *object_eval, bool & /*r_needsfree*/) +{ + return BKE_object_get_evaluated_mesh(object_eval); +} + +} // namespace USD diff --git a/source/blender/io/usd/intern/usd_writer_mesh.h b/source/blender/io/usd/intern/usd_writer_mesh.h new file mode 100644 index 00000000000..4175e2b7e27 --- /dev/null +++ b/source/blender/io/usd/intern/usd_writer_mesh.h @@ -0,0 +1,66 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ +#ifndef __USD_WRITER_MESH_H__ +#define __USD_WRITER_MESH_H__ + +#include "usd_writer_abstract.h" + +#include <pxr/usd/usdGeom/mesh.h> + +namespace USD { + +struct USDMeshData; + +/* Writer for USD geometry. Does not assume the object is a mesh object. */ +class USDGenericMeshWriter : public USDAbstractWriter { + public: + USDGenericMeshWriter(const USDExporterContext &ctx); + + protected: + virtual bool is_supported(const HierarchyContext *context) const override; + virtual void do_write(HierarchyContext &context) override; + + virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) = 0; + virtual void free_export_mesh(Mesh *mesh); + + private: + /* Mapping from material slot number to array of face indices with that material. */ + typedef std::map<short, pxr::VtIntArray> MaterialFaceGroups; + + void write_mesh(HierarchyContext &context, Mesh *mesh); + void get_geometry_data(const Mesh *mesh, struct USDMeshData &usd_mesh_data); + void assign_materials(const HierarchyContext &context, + pxr::UsdGeomMesh usd_mesh, + const MaterialFaceGroups &usd_face_groups); + void write_uv_maps(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh); + void write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh); + void write_surface_velocity(Object *object, const Mesh *mesh, pxr::UsdGeomMesh usd_mesh); +}; + +class USDMeshWriter : public USDGenericMeshWriter { + public: + USDMeshWriter(const USDExporterContext &ctx); + + protected: + virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override; +}; + +} // namespace USD + +#endif /* __USD_WRITER_MESH_H__ */ diff --git a/source/blender/io/usd/intern/usd_writer_metaball.cc b/source/blender/io/usd/intern/usd_writer_metaball.cc new file mode 100644 index 00000000000..25b216d20f3 --- /dev/null +++ b/source/blender/io/usd/intern/usd_writer_metaball.cc @@ -0,0 +1,81 @@ +/* + * 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) 2020 Blender Foundation. + * All rights reserved. + */ +#include "usd_writer_metaball.h" +#include "usd_hierarchy_iterator.h" + +#include <pxr/usd/usdGeom/mesh.h> +#include <pxr/usd/usdShade/material.h> +#include <pxr/usd/usdShade/materialBindingAPI.h> + +extern "C" { +#include "BLI_assert.h" + +#include "BKE_displist.h" +#include "BKE_lib_id.h" +#include "BKE_mball.h" +#include "BKE_mesh.h" +#include "BKE_object.h" + +#include "DNA_mesh_types.h" +#include "DNA_meta_types.h" +} + +namespace USD { + +USDMetaballWriter::USDMetaballWriter(const USDExporterContext &ctx) : USDGenericMeshWriter(ctx) +{ +} + +bool USDMetaballWriter::is_supported(const HierarchyContext *context) const +{ + Scene *scene = DEG_get_input_scene(usd_export_context_.depsgraph); + return is_basis_ball(scene, context->object) && USDGenericMeshWriter::is_supported(context); +} + +bool USDMetaballWriter::check_is_animated(const HierarchyContext & /*context*/) const +{ + /* We assume that metaballs are always animated, as the current object may + * not be animated but another ball in the same group may be. */ + return true; +} + +Mesh *USDMetaballWriter::get_export_mesh(Object *object_eval, bool &r_needsfree) +{ + Mesh *mesh_eval = BKE_object_get_evaluated_mesh(object_eval); + if (mesh_eval != nullptr) { + /* Mesh_eval only exists when generative modifiers are in use. */ + r_needsfree = false; + return mesh_eval; + } + r_needsfree = true; + return BKE_mesh_new_from_object(usd_export_context_.depsgraph, object_eval, false); +} + +void USDMetaballWriter::free_export_mesh(Mesh *mesh) +{ + BKE_id_free(nullptr, mesh); +} + +bool USDMetaballWriter::is_basis_ball(Scene *scene, Object *ob) const +{ + Object *basis_ob = BKE_mball_basis_find(scene, ob); + return ob == basis_ob; +} + +} // namespace USD diff --git a/source/blender/io/usd/intern/usd_writer_metaball.h b/source/blender/io/usd/intern/usd_writer_metaball.h new file mode 100644 index 00000000000..1a86daae2ae --- /dev/null +++ b/source/blender/io/usd/intern/usd_writer_metaball.h @@ -0,0 +1,42 @@ +/* + * 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) 2020 Blender Foundation. + * All rights reserved. + */ +#ifndef __USD_WRITER_METABALL_H__ +#define __USD_WRITER_METABALL_H__ + +#include "usd_writer_mesh.h" + +namespace USD { + +class USDMetaballWriter : public USDGenericMeshWriter { + public: + USDMetaballWriter(const USDExporterContext &ctx); + + protected: + virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override; + virtual void free_export_mesh(Mesh *mesh) override; + virtual bool is_supported(const HierarchyContext *context) const override; + virtual bool check_is_animated(const HierarchyContext &context) const override; + + private: + bool is_basis_ball(Scene *scene, Object *ob) const; +}; + +} // namespace USD + +#endif /* __USD_WRITER_METABALL_H__ */ diff --git a/source/blender/io/usd/intern/usd_writer_transform.cc b/source/blender/io/usd/intern/usd_writer_transform.cc new file mode 100644 index 00000000000..321b516221a --- /dev/null +++ b/source/blender/io/usd/intern/usd_writer_transform.cc @@ -0,0 +1,64 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ +#include "usd_writer_transform.h" +#include "usd_hierarchy_iterator.h" + +#include <pxr/base/gf/matrix4f.h> +#include <pxr/usd/usdGeom/xform.h> + +extern "C" { +#include "BKE_object.h" + +#include "BLI_math_matrix.h" + +#include "DNA_layer_types.h" +} + +namespace USD { + +USDTransformWriter::USDTransformWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx) +{ +} + +void USDTransformWriter::do_write(HierarchyContext &context) +{ + float parent_relative_matrix[4][4]; // The object matrix relative to the parent. + mul_m4_m4m4(parent_relative_matrix, context.parent_matrix_inv_world, context.matrix_world); + + // Write the transform relative to the parent. + pxr::UsdGeomXform xform = pxr::UsdGeomXform::Define(usd_export_context_.stage, + usd_export_context_.usd_path); + if (!xformOp_) { + xformOp_ = xform.AddTransformOp(); + } + xformOp_.Set(pxr::GfMatrix4d(parent_relative_matrix), get_export_time_code()); +} + +bool USDTransformWriter::check_is_animated(const HierarchyContext &context) const +{ + if (context.duplicator != NULL) { + /* This object is being duplicated, so could be emitted by a particle system and thus + * influenced by forces. TODO(Sybren): Make this more strict. Probably better to get from the + * depsgraph whether this object instance has a time source. */ + return true; + } + return BKE_object_moves_in_time(context.object, context.animation_check_include_parent); +} + +} // namespace USD diff --git a/source/blender/io/usd/intern/usd_writer_transform.h b/source/blender/io/usd/intern/usd_writer_transform.h new file mode 100644 index 00000000000..52c4a657f33 --- /dev/null +++ b/source/blender/io/usd/intern/usd_writer_transform.h @@ -0,0 +1,42 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ +#ifndef __USD_WRITER_TRANSFORM_H__ +#define __USD_WRITER_TRANSFORM_H__ + +#include "usd_writer_abstract.h" + +#include <pxr/usd/usdGeom/xform.h> + +namespace USD { + +class USDTransformWriter : public USDAbstractWriter { + private: + pxr::UsdGeomXformOp xformOp_; + + public: + USDTransformWriter(const USDExporterContext &ctx); + + protected: + void do_write(HierarchyContext &context) override; + bool check_is_animated(const HierarchyContext &context) const override; +}; + +} // namespace USD + +#endif /* __USD_WRITER_TRANSFORM_H__ */ diff --git a/source/blender/io/usd/usd.h b/source/blender/io/usd/usd.h new file mode 100644 index 00000000000..8a5575d53cf --- /dev/null +++ b/source/blender/io/usd/usd.h @@ -0,0 +1,63 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ + +#ifndef __USD_H__ +#define __USD_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "DEG_depsgraph.h" + +struct Scene; +struct bContext; + +struct USDExportParams { + bool export_animation; + bool export_hair; + bool export_uvmaps; + bool export_normals; + bool export_materials; + bool selected_objects_only; + bool use_instancing; + enum eEvaluationMode evaluation_mode; +}; + +/* The USD_export takes a as_background_job parameter, and returns a boolean. + * + * When as_background_job=true, returns false immediately after scheduling + * a background job. + * + * When as_background_job=false, performs the export synchronously, and returns + * true when the export was ok, and false if there were any errors. + */ + +bool USD_export(struct bContext *C, + const char *filepath, + const struct USDExportParams *params, + bool as_background_job); + +int USD_get_version(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __USD_H__ */ |