From 09eec16195c6f78cf57fce06c739cd8376626d6c Mon Sep 17 00:00:00 2001 From: SergeyShlyaev <63272131+SergeyShlyaev@users.noreply.github.com> Date: Thu, 20 Aug 2020 23:01:59 -0400 Subject: [PATCH 1/6] Added support for alembic meshes (www.alembic.io) Alembic is an open source file format for exchanging 3D computer animation information between different platforms and applications. Any app can write their 3D information out in the Alembic format which can then be read in by any other app that supports Alembic, such as Maya, 3dsmax, Houdini, Katana, Nuke to name a few. Beyond that, it stores 3D geometry in a format that is very efficient with a small file size that is quick to read and unpack. --- src/shapes/CMakeLists.txt | 1 + src/shapes/abc.cpp | 608 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 609 insertions(+) create mode 100644 src/shapes/abc.cpp diff --git a/src/shapes/CMakeLists.txt b/src/shapes/CMakeLists.txt index bf9279cec..45c36c61a 100644 --- a/src/shapes/CMakeLists.txt +++ b/src/shapes/CMakeLists.txt @@ -4,6 +4,7 @@ add_plugin(obj obj.cpp) add_plugin(ply ply.cpp) add_plugin(blender blender.cpp) add_plugin(serialized serialized.cpp) +add_plugin(abc abc.cpp) add_plugin(cylinder cylinder.cpp) add_plugin(disk disk.cpp) diff --git a/src/shapes/abc.cpp b/src/shapes/abc.cpp new file mode 100644 index 000000000..3616b5ac1 --- /dev/null +++ b/src/shapes/abc.cpp @@ -0,0 +1,608 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Alembic Includes +#include +#include +#include +#include + +NAMESPACE_BEGIN(mitsuba) + +/**! + +.. _shape-abc: + +ABC (Alembic) mesh loader (:monosp:`abc`) +---------------------------------------------------------- + +.. pluginparameters:: + + * - filename + - |string| + - Filename of the ABC file that should be loaded + * - shape_index + - |int| + - A :monosp:`.serialized` file may contain several separate meshes. This parameter + specifies which one should be loaded. (Default: 0, i.e. the first one) + * - m_flip_normals + - |bool| + - Will flip normals on the mesh. Some programs output face indexes in clockwise order, some in counter-clockwise. + This parameter optionally fixes handedness. (Default: |true|) + * - load_visible + - |bool| + - Alembic allows for objects or specific faces of an object to be flagged as either visible or hidden. + This is useful because it allows objects or faces to appear or disaper over time, by animating the visiblity property. + Setting this to |true| will load only objects flagged as visible. (Default: |false|) + * - face_normals + - |bool| + - When set to |true|, any existing or computed vertex normals are + discarded and *face normals* will instead be used during rendering. + This gives the rendered object a faceted appearance. (Default: |false|) + * - flip_tex_coords + - |bool| + - Treat the vertical component of the texture as inverted? (Default: |true|) + * - to_world + - |Transform4f| + - Specifies an optional linear object-to-world Transform4fation. + (Default: none, i.e. object space = world space) + +This plugin implements loader for Alembic file format (www.alembic.io). The +current plugin implementation supports arbitrary meshes with optional UV +coordinates, vertex normals and other custom vertex or face attributes. + + */ + + +using namespace std; +using namespace Alembic::AbcGeom; +using namespace Alembic::AbcCoreFactory; +using namespace Alembic::AbcCoreAbstract; + +template +class AlembicMesh final : public Mesh { +public: + MTS_IMPORT_BASE(Mesh, m_name, m_bbox, m_to_world, m_vertex_count, m_face_count, + m_vertex_positions_buf, m_vertex_normals_buf, m_vertex_texcoords_buf, + m_faces_buf, add_attribute, m_disable_vertex_normals, has_vertex_normals, + has_vertex_texcoords, recompute_vertex_normals, set_children) + MTS_IMPORT_TYPES() + + using typename Base::MeshAttributeType; + using typename Base::ScalarSize; + using typename Base::ScalarIndex; + using typename Base::InputFloat; + using typename Base::InputPoint3f; + using typename Base::InputVector2f; + using typename Base::InputVector3f; + using typename Base::InputNormal3f; + using typename Base::FloatStorage; + + using ScalarIndex3 = std::array; + + struct AlembicPolymesh + { + std::string id; + IPolyMeshSchema schema; + ScalarTransform4f xform; + }; + + struct VertexBinding { + ScalarIndex3 key {{ 0, 0, 0 }}; + ScalarIndex value { 0 }; + VertexBinding *next { nullptr }; + }; + + bool m_flip_normals = true; + bool m_load_visible = false; + std::string m_mesh_name; + + // Alembics can store animation data + // For now use values at 0 time until Mitsuba supports animaiton + double m_alembic_time = 0; + + AlembicMesh(const Properties &props) : Base(props) { + + auto fs = Thread::thread()->file_resolver(); + fs::path file_path = fs->resolve(props.string("filename")); + m_name = file_path.filename().string(); + + // Some DCC output face indices in clockwise order, some in counter-clockwise. + // It looks like Mitsuba expects opposite order compared to most common DCC's + // Optionally fix handedness to avoid black renders. + m_flip_normals = props.bool_("flip_normals", true); + m_load_visible = props.bool_("load_visible", false); + const uint shape_index = props.int_("shape_index", 0); + const bool flip_tex_coords = props.bool_("flip_tex_coords", true); + + auto fail = [&](const std::string &descr) { + Throw("Error while loading Alembic file \"%s\": %s!", m_name, descr); + }; + auto detailed_fail = [&](const std::string &descr, std::string m_mesh_name="") { + Throw("Error while loading Alembic file \"%s\" at object \"%s\". %s!", m_name, m_mesh_name, descr); + }; + + Log(Debug, "Loading mesh from \"%s\" ..", m_name); + if (!fs::exists(file_path)) + fail("file not found"); + + IFactory factory; + IFactory::CoreType core_type; + IArchive archive = factory.getArchive(file_path.string(), core_type); + IObject archive_top = archive.getTop(); + + index_t sample_index = 0; + std::vector alembic_polymeshes; + ScalarTransform4f alembic_transform; + + // how do we create multiple mesh shapes? + // for now create just one shape specified by shape_index + if (shape_index > (uint) archive_top.getNumChildren() - 1) + fail(tfm::format("Unable to load mesh. Shape index \"%i\" is " + "out of range 0..%i", + shape_index, archive_top.getNumChildren() - 1)); + + IObject object = archive_top.getChild(shape_index); + ScalarTransform4f obj_xform = read_abc_xform(object); + find_children_recursively(object, sample_index, alembic_transform, alembic_polymeshes); + + if (alembic_polymeshes.empty()) + fail("found no polygonal meshes in file."); + + for (size_t each_alembic = 0; each_alembic < alembic_polymeshes.size(); ++each_alembic) + { + m_mesh_name = alembic_polymeshes[each_alembic].id; + IPolyMeshSchema poly_schema = alembic_polymeshes[each_alembic].schema; + IPolyMeshSchema::Sample sample = poly_schema.getValue(sample_index); + IN3fGeomParam normals_param = poly_schema.getNormalsParam(); + IV2fGeomParam uvs_param = poly_schema.getUVsParam(); + ICompoundProperty arbitrary_properties = poly_schema.getArbGeomParams(); + alembic_polymeshes[each_alembic].xform = obj_xform * alembic_polymeshes[each_alembic].xform; + + const bool polymesh_has_uvs = uvs_param.valid() && uvs_param.getValueProperty().valid(); + const bool polymesh_has_vertex_normals = normals_param.valid() && normals_param.getValueProperty().valid(); + + size_t num_polygons=sample.getFaceCounts()->size(); + P3fArraySamplePtr positions = sample.getPositions(); + + std::vector vertices; + std::vector normals; + std::vector texcoords; + std::vector vertex_map; + + for (size_t i = 0, e = positions->size(); i < e; ++i){ + InputPoint3f p = alembic_polymeshes[each_alembic].xform.transform_affine( + InputPoint3f( (*positions)[i][0], (*positions)[i][1], (*positions)[i][2] ) ); + p = m_to_world.transform_affine(p); + if (unlikely(!all(enoki::isfinite(p)))) + detailed_fail("Alembic mesh contains invalid vertex position data", m_mesh_name); + m_bbox.expand(p); + vertices.push_back(p); + } + + Int32ArraySamplePtr alembic_face_indices = sample.getFaceIndices(); + std::vector indices; + indices.reserve( alembic_face_indices->size() ); + + Int32ArraySamplePtr vertices_per_face = sample.getFaceCounts(); + size_t mesh_face_index=0; + ScalarIndex vertex_ctr = 0; + + if (polymesh_has_uvs) + { + make_attr( + uvs_param, num_polygons, vertices_per_face, texcoords); + if (flip_tex_coords){ + for (size_t i = 0; i < texcoords.size(); i++) + { + InputVector2f uv = texcoords[i]; + uv.y() = 1.f - uv.y(); + texcoords[i] = uv; + } + } + } + + if (!m_disable_vertex_normals && polymesh_has_vertex_normals) + { + make_attr( + normals_param, num_polygons, vertices_per_face, normals); + + for (size_t i = 0; i < normals.size(); i++) + { + InputNormal3f n = normals[i]; + n = normalize(m_to_world.transform_affine(n)); + if ( unlikely(!all(enoki::isfinite(n))) ){ + Log(Warn, "Invalid vertex normal: %s at vertex %s." , n, i ); + } + normals[i] = n; + } + } + + size_t uv_ctr=0; + for (size_t each_face=0; each_facecurrent_face_indices; + current_face_indices.reserve( vertex_count_in_face ); + std::vector uvs_per_face; + for (size_t vertex_index = 0; vertex_indexkey != key && entry->next != nullptr) + entry = entry->next; + + ScalarIndex id; + if (entry->key == key) { + // Hit + id = entry->value; + } else { + // Miss + if (entry->key != ScalarIndex3{{0, 0, 0}}) { + entry->next = new VertexBinding(); + entry = entry->next; + } + entry->key = key; + id = entry->value = vertex_ctr++; + } + + if (vertex_index < 3) { + tri[vertex_index] = id; + } else { + tri[1] = tri[2]; + tri[2] = id; + } + vertex_index++; + if (vertex_index >= 3){ + indices.push_back(tri); + } + uv_ctr++; + } + } + + m_vertex_count = vertex_ctr; + m_face_count = (ScalarSize)indices.size(); + m_faces_buf = DynamicBuffer::copy(indices.data(), m_face_count * 3); + m_vertex_positions_buf = empty(m_vertex_count * 3); + + if (!m_disable_vertex_normals){ + m_vertex_normals_buf = empty(m_vertex_count * 3); + } + + if (!texcoords.empty()){ + m_vertex_texcoords_buf = empty(m_vertex_count*2); + } + + m_faces_buf.managed(); + m_vertex_positions_buf.managed(); + m_vertex_normals_buf.managed(); + m_vertex_texcoords_buf.managed(); + + for (const auto& v_ : vertex_map) { + const VertexBinding *v = &v_; + + while (v && v->key != ScalarIndex3{{0, 0, 0}}) { + InputFloat* position_ptr = m_vertex_positions_buf.data() + v->value * 3; + InputFloat* normal_ptr = m_vertex_normals_buf.data() + v->value * 3; + InputFloat* texcoord_ptr = m_vertex_texcoords_buf.data() + v->value * 2; + auto key = v->key; + + store_unaligned(position_ptr, vertices[key[0]]); + + if (key[1] && polymesh_has_uvs) + store_unaligned(texcoord_ptr, texcoords[key[1] - 1]); + + if (!m_disable_vertex_normals && polymesh_has_vertex_normals && key[1]) + store_unaligned(normal_ptr, normals[key[1] - 1]); + + v = v->next; + } + } + + if (arbitrary_properties.valid()) + add_arbitrary_geom_params(arbitrary_properties); + + if (!m_disable_vertex_normals && normals.empty()) { + Timer timer2; + recompute_vertex_normals(); + Log(Warn, "\"%s\": computed vertex normals for %s (took %s)", m_name, m_mesh_name, + util::time_string(timer2.value())); + } + } + if constexpr (is_cuda_array_v) + cuda_sync(); + + set_children(); + } + +private: + std::unordered_map known_attr = { + {"Cd","color"}, + {"color","color"}, + {"transparency","alpha"}, + {"v","velocity"} + }; + + void add_arbitrary_geom_params(ICompoundProperty parent) + { + for (size_t i = 0; i < parent.getNumProperties(); ++i){ + const PropertyHeader &property_header = parent.getPropertyHeader(i); + + if (IFloatGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + + } + else if (IHalfGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + + } + else if (IDoubleGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + + } + else if (ICharGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + + } + else if (IInt16GeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IInt32GeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IInt64GeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IStringGeomParam::matches(property_header)){ + // shop_material path + process_arbitrary_geom_param( + parent, property_header); + } + else if (IV2fGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IV2dGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IV3fGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IV3dGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IN3fGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IN3dGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IC3hGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IC3fGeomParam::matches(property_header)){ + // e.g. vertex color attribute + process_arbitrary_geom_param( + parent, property_header); + } + else if (IC3cGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IP3fGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IP3dGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IBoolGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + } + } + + template + void process_arbitrary_geom_param( + ICompoundProperty parent, + const PropertyHeader & property_header ) + { + std::string attr_name = property_header.getName(); + geom_param_t param(parent, property_header.getName()); + ISampleSelector sample_selector( m_alembic_time ); // alembic_time, for now just use value at 0 time + typename geom_param_t::sample_type param_sample; + param.getExpanded(param_sample, sample_selector); + + // 3 for color; 1 for shop_materialpath + size_t extent = geom_param_t::prop_type::traits_type::dataType().getExtent(); + const pod_t *values = reinterpret_cast( + param_sample.getVals()->get()); + + switch (param_sample.getScope()) + { + case kVaryingScope: + case kVertexScope: + //Point attribute + if ( !(known_attr.find(attr_name) == known_attr.end()) ) + attr_name = "vertex_"+known_attr[attr_name]; + add_attribute(attr_name, extent, FloatStorage::copy(values, m_vertex_count * extent)); + break; + case kFacevaryingScope:{ + // Vertex attribute + if ( !(known_attr.find(attr_name) == known_attr.end()) ) + attr_name = "vertex_"+known_attr[attr_name]; + + bool is_vertex_attr = attr_name.find("vertex_") == 0; + if(is_vertex_attr){ + add_attribute(attr_name, extent, FloatStorage::copy(values, m_vertex_count * extent)); + } + else{ + Log(Info, "Skipping vertex attribute \"%s\" on \"%s\" as it does not start with \"vertex_\"", attr_name, m_mesh_name); + } + break; + } + case kConstantScope: + case kUnknownScope: + case kUniformScope:{ + // Known attribute + if ( !(known_attr.find(attr_name) == known_attr.end()) ) + attr_name = "face_"+known_attr[attr_name]; + + bool is_face_attr = attr_name.find("face_") == 0; + if(is_face_attr){ + add_attribute(attr_name, extent, FloatStorage::copy(values, m_face_count * extent)); + } + else{ + Log(Info, "Skipping face attribute \"%s\" on \"%s\" as it does not start with \"face_\"", attr_name, m_mesh_name); + } + } + } + } + + template + void make_attr(geom_param_t param, + size_t num_polygons, + Int32ArraySamplePtr &vertices_per_face, + std::vector &data_array ) + { + typename geom_param_t::sample_type param_sample; + param.getExpanded(param_sample, ISampleSelector( m_alembic_time )); //abc time, for now take value at 0 time + size_t extent = geom_param_t::prop_type::traits_type::dataType().getExtent(); + ptr_t param_sample_values = param_sample.getVals(); + + size_t data_index=0; + for (size_t each_face=0; each_face data_per_face; + + for (size_t vertex_index=0; vertex_index& map) + { + for (size_t i = 0; i < obj.getNumChildren(); i++) + { + ScalarTransform4f child_transform = t; + IObject child = obj.getChild(i); + + if ( IXform::matches(child.getMetaData()) ){ + IXform xform_obj = IXform(child, kWrapExisting); + IXformSchema &xs = xform_obj.getSchema(); + XformSample xform_sample = xs.getValue(ISampleSelector(m_alembic_time)); + + ScalarTransform4f obj_xform = read_abc_xform(child); + if (xform_sample.getInheritsXforms()){ + child_transform = child_transform * obj_xform; + } + else{ + child_transform = obj_xform; + } + } + else if (IPolyMesh::matches(child.getMetaData())){ + IPolyMesh poly_obj(child, kWrapExisting); + AlembicPolymesh polymesh; + polymesh.schema = poly_obj.getSchema(); + polymesh.id = child.getFullName(); + polymesh.xform = t; + if (!m_load_visible || GetVisibilityProperty(child) == (bool)kVisibilityVisible){ + map.push_back(polymesh); + } + else if (GetVisibilityProperty(child) == (bool)kVisibilityHidden){ + // Issue a message in the log for invisible object perhaps + } + continue; + } + // continue traversing alembic tree for more child objects + find_children_recursively(child, sample_index, child_transform, map); + } + } + + ScalarTransform4f read_abc_xform(const IObject& obj) + { + ScalarTransform4f obj_xform; + if (IXform::matches(obj.getMetaData()) ){ + IXform xform_obj = IXform(obj, kWrapExisting); + IXformSchema &xs = xform_obj.getSchema(); + XformSample xform_sample = xs.getValue(ISampleSelector(m_alembic_time)); + + if (xform_sample.getInheritsXforms()){ + M44d m = xform_sample.getMatrix(); + for (size_t i = 0; i < 4; ++i){ + for (size_t j = 0; j < 4; ++j){ + obj_xform.matrix[i][j] = m[i][j]; + } + } + } + } + return obj_xform; + } + + MTS_DECLARE_CLASS() +}; + +MTS_IMPLEMENT_CLASS_VARIANT(AlembicMesh, Mesh) +MTS_EXPORT_PLUGIN(AlembicMesh, "Alembic Mesh") +NAMESPACE_END(mitsuba) + + From 29b5c557fa8f736dfc5c64ed5a531e45c18014b7 Mon Sep 17 00:00:00 2001 From: SergeyShlyaev <63272131+SergeyShlyaev@users.noreply.github.com> Date: Mon, 31 Aug 2020 22:14:17 -0400 Subject: [PATCH 2/6] Support for loading multiple meshes Added support for loading multiple meshes from file Added support for `shape_name` to specify object by name --- abc.cpp | 658 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 658 insertions(+) create mode 100644 abc.cpp diff --git a/abc.cpp b/abc.cpp new file mode 100644 index 000000000..155b685c6 --- /dev/null +++ b/abc.cpp @@ -0,0 +1,658 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Alembic Includes +#include +#include +#include +#include + +NAMESPACE_BEGIN(mitsuba) + +/**! + +.. _shape-abc: + +ABC (Alembic) mesh loader (:monosp:`abc`) +---------------------------------------------------------- + +.. pluginparameters:: + + * - filename + - |string| + - Filename of the Alembic file to load + * - shape_name + - |string| + - Alembic file may contain several separate meshes. This optional parameter + specifies name of mesh to load. (Default: not specified, i.e. load all objects) + * - shape_index + - |int| + - Alembic file may contain several separate meshes. This optional parameter + specifies which mesh should be loaded. (Default: 0, i.e. the first one) If both + shape_name and shape_index are specified, shape_name takes precedence. + * - m_flip_normals + - |bool| + - Will flip normals on the mesh. Some programs output face indexes in clockwise order, some in counter-clockwise. + This parameter optionally fixes handedness. (Default: |true|) + * - load_visible + - |bool| + - Alembic allows for objects or specific faces of an object to be flagged as either visible or hidden. + This is useful because it allows objects or faces to appear or disaper over time, by animating the visiblity property. + Setting this to |true| will load only objects flagged as visible. (Default: |false|) + * - face_normals + - |bool| + - When set to |true|, any existing or computed vertex normals are + discarded and *face normals* will instead be used during rendering. + This gives the rendered object a faceted appearance. (Default: |false|) + * - flip_tex_coords + - |bool| + - Treat the vertical component of the texture as inverted? (Default: |true|) + * - to_world + - |Transform4f| + - Specifies an optional linear object-to-world Transform4fation. + (Default: none, i.e. object space = world space) + +This plugin implements loader for Alembic file format (www.alembic.io). The +current plugin implementation supports arbitrary meshes with optional UV +coordinates, vertex normals and other custom vertex or face attributes. + + */ + + +using namespace std; +using namespace Alembic::AbcGeom; +using namespace Alembic::AbcCoreFactory; +using namespace Alembic::AbcCoreAbstract; + +template +class AlembicMesh final : public Mesh { +public: + MTS_IMPORT_BASE(Mesh, m_bsdf, m_name, m_bbox, m_to_world, m_vertex_count, m_face_count, + m_vertex_positions_buf, m_vertex_normals_buf, m_vertex_texcoords_buf, + m_faces_buf, m_disable_vertex_normals, has_vertex_normals, + has_vertex_texcoords, recompute_vertex_normals, set_children) + MTS_IMPORT_TYPES(BSDF, Shape) + + // Mesh is always stored in single precision + using InputFloat = float; + using InputPoint3f = Point; + using InputVector2f = Vector; + using InputVector3f = Vector; + using InputNormal3f = Normal; + using FloatStorage = DynamicBuffer>; + + using typename Base::ScalarSize; + using typename Base::ScalarIndex; + using typename Base::MeshAttributeType; + using ScalarIndex3 = std::array; + + struct AlembicPolymesh + { + std::string id; + IPolyMeshSchema schema; + ScalarTransform4f xform; + }; + + struct VertexBinding { + ScalarIndex3 key {{ 0, 0, 0 }}; + ScalarIndex value { 0 }; + VertexBinding *next { nullptr }; + }; + + bool m_flip_normals = true; + bool m_load_visible = false; + + // Alembics can store animation data + // For now use values at 0 time until Mitsuba supports animaiton + double m_alembic_time = 0; + + AlembicMesh(const Properties &props) : Base(props) { + + auto fs = Thread::thread()->file_resolver(); + fs::path file_path = fs->resolve(props.string("filename")); + m_name = file_path.filename().string(); + + // Some DCC output face indices in clockwise order, some in counter-clockwise. + // It looks like Mitsuba expects opposite order compared to most common DCC's + // Optionally fix handedness to avoid black renders. + m_flip_normals = props.bool_("flip_normals", true); + m_load_visible = props.bool_("load_visible", false); + const long shape_index = props.int_("shape_index", -1); + const bool flip_tex_coords = props.bool_("flip_tex_coords", true); + m_shape_name = props.string("shape_name", ""); + m_use_shape_name = false; + bool use_shape_index = false; + + if (!m_shape_name.empty()) + m_use_shape_name = true; + + // Use shape_index if it is provided and shape_name is not used + if (shape_index != -1 && !m_use_shape_name) + use_shape_index = true; + + auto fail = [&](const std::string &descr) { + Throw("Error while loading Alembic file \"%s\": %s.", m_name, descr); + }; + auto detailed_fail = [&](const std::string &descr, std::string m_mesh_name="") { + Throw("Error while loading Alembic file \"%s\" at object \"%s\". %s.", m_name, m_mesh_name, descr); + }; + + Log(Debug, "Loading mesh from \"%s\" ..", m_name); + if (!fs::exists(file_path)) + fail("file not found"); + + IFactory factory; + IFactory::CoreType core_type; + IArchive archive = factory.getArchive(file_path.string(), core_type); + IObject archive_top = archive.getTop(); + + index_t sample_index = 0; + std::vector alembic_polymeshes; + ScalarTransform4f alembic_transform; + + // create one mesh specified by shape_index + if (use_shape_index && (shape_index > (uint) archive_top.getNumChildren() - 1) ) + fail(tfm::format("Unable to load mesh. Shape index \"%i\" is " + "out of range 0..%i", + shape_index, archive_top.getNumChildren() - 1)); + + if (use_shape_index) + archive_top = archive_top.getChild(shape_index); + + ScalarTransform4f obj_xform = read_abc_xform(archive_top); + find_children_recursively(archive_top, sample_index, alembic_transform, alembic_polymeshes); + + if (alembic_polymeshes.empty()){ + std::string error_msg = "found no polygonal meshes in file"; + if (m_use_shape_name) + error_msg += tfm::format(". Got shape_name \"%s\" " + "please check if this shape was exported into Alembic file" , m_shape_name); + if (use_shape_index) + error_msg += tfm::format(". Got shape_index \"%i\" " + "could not get polymesh. Object at \"%i\" is \"%s\"" , + shape_index, shape_index, archive_top.getFullName()); + fail(error_msg); + } + + for (size_t each_alembic = 0; each_alembic < alembic_polymeshes.size(); each_alembic++){ + m_mesh_name = alembic_polymeshes[each_alembic].id; + + IPolyMeshSchema poly_schema = alembic_polymeshes[each_alembic].schema; + IPolyMeshSchema::Sample sample = poly_schema.getValue(sample_index); + IN3fGeomParam normals_param = poly_schema.getNormalsParam(); + IV2fGeomParam uvs_param = poly_schema.getUVsParam(); + ICompoundProperty arbitrary_properties = poly_schema.getArbGeomParams(); + alembic_polymeshes[each_alembic].xform = obj_xform * alembic_polymeshes[each_alembic].xform; + + const bool polymesh_has_uvs = uvs_param.valid() && uvs_param.getValueProperty().valid(); + const bool polymesh_has_vertex_normals = normals_param.valid() && normals_param.getValueProperty().valid(); + + size_t num_polygons=sample.getFaceCounts()->size(); + P3fArraySamplePtr positions = sample.getPositions(); + + std::vector vertices; + std::vector normals; + std::vector texcoords; + std::vector vertex_map; + + for (size_t i = 0, e = positions->size(); i < e; ++i){ + InputPoint3f p = alembic_polymeshes[each_alembic].xform.transform_affine( + InputPoint3f( (*positions)[i][0], (*positions)[i][1], (*positions)[i][2] ) ); + p = m_to_world.transform_affine(p); + if (unlikely(!all(enoki::isfinite(p)))) + detailed_fail("Alembic mesh contains invalid vertex position data", m_mesh_name); + m_bbox.expand(p); + vertices.push_back(p); + } + + Int32ArraySamplePtr alembic_face_indices = sample.getFaceIndices(); + std::vector indices; + indices.reserve( alembic_face_indices->size() ); + + Int32ArraySamplePtr vertices_per_face = sample.getFaceCounts(); + size_t mesh_face_index=0; + ScalarIndex vertex_ctr = 0; + + if (polymesh_has_uvs) + { + make_attr( + uvs_param, num_polygons, vertices_per_face, texcoords); + if (flip_tex_coords){ + for (size_t i = 0; i < texcoords.size(); i++) + { + InputVector2f uv = texcoords[i]; + uv.y() = 1.f - uv.y(); + texcoords[i] = uv; + } + } + } + + if (!m_disable_vertex_normals && polymesh_has_vertex_normals) + { + make_attr( + normals_param, num_polygons, vertices_per_face, normals); + + for (size_t i = 0; i < normals.size(); i++) + { + InputNormal3f n = normals[i]; + n = normalize(m_to_world.transform_affine(n)); + if ( unlikely(!all(enoki::isfinite(n))) ){ + Log(Warn, + "Invalid vertex normal: %s at vertex %s of %s.", n, i, m_mesh_name); + } + normals[i] = n; + } + } + + size_t uv_ctr=0; + for (size_t each_face=0; each_facecurrent_face_indices; + current_face_indices.reserve( vertex_count_in_face ); + std::vector uvs_per_face; + for (size_t vertex_index = 0; vertex_indexkey != key && entry->next != nullptr) + entry = entry->next; + + ScalarIndex id; + if (entry->key == key) { + // Hit + id = entry->value; + } else { + // Miss + if (entry->key != ScalarIndex3{{0, 0, 0}}) { + entry->next = new VertexBinding(); + entry = entry->next; + } + entry->key = key; + id = entry->value = vertex_ctr++; + } + + if (vertex_index < 3) { + tri[vertex_index] = id; + } else { + tri[1] = tri[2]; + tri[2] = id; + } + vertex_index++; + if (vertex_index >= 3){ + indices.push_back(tri); + } + uv_ctr++; + } + } + + m_vertex_count = vertex_ctr; + m_face_count = (ScalarSize)indices.size(); + m_vertex_positions_buf = empty(m_vertex_count * 3); + m_faces_buf = DynamicBuffer::copy(indices.data(), m_face_count * 3); + + + if (!m_disable_vertex_normals){ + m_vertex_normals_buf = empty(m_vertex_count * 3); + } + + if (!texcoords.empty()){ + m_vertex_texcoords_buf = empty(m_vertex_count*2); + } + + for (const auto& v_ : vertex_map) { + const VertexBinding *v = &v_; + + while (v && v->key != ScalarIndex3{{0, 0, 0}}) { + InputFloat* position_ptr = m_vertex_positions_buf.data() + v->value * 3; + InputFloat* normal_ptr = m_vertex_normals_buf.data() + v->value * 3; + InputFloat* texcoord_ptr = m_vertex_texcoords_buf.data() + v->value * 2; + auto key = v->key; + + store_unaligned(position_ptr, vertices[key[0]]); + + if (key[1] && polymesh_has_uvs) + store_unaligned(texcoord_ptr, texcoords[key[1] - 1]); + + if (!m_disable_vertex_normals && polymesh_has_vertex_normals && key[1]) + store_unaligned(normal_ptr, normals[key[1] - 1]); + + v = v->next; + } + } + + // Mesh constructor in mesh.cpp already has managed(), cuda_sync() + // and set_children() calls, so we don't need to call it here + m_mesh = new Mesh(m_mesh_name, m_vertex_count, m_face_count, + props, polymesh_has_vertex_normals, polymesh_has_uvs); + + m_mesh->vertex_positions_buffer() = m_vertex_positions_buf; + m_mesh->faces_buffer() = m_faces_buf; + m_mesh->vertex_normals_buffer() = m_vertex_normals_buf; + m_mesh->vertex_texcoords_buffer() = m_vertex_texcoords_buf; + + m_mesh->recompute_bbox(); + if (!m_disable_vertex_normals && normals.empty()) { + Timer timer2; + m_mesh->recompute_vertex_normals(); + Log(Warn, "\"%s\": computed vertex normals for %s (took %s)", m_name, m_mesh_name, + util::time_string(timer2.value())); + } + + if (arbitrary_properties.valid()) + add_arbitrary_geom_params(arbitrary_properties); + + m_mesh_objects.push_back(m_mesh); + } + } + + std::vector> expand() const override { + return m_mesh_objects; + } + +protected: + std::string m_mesh_name; + +private: + ref> m_mesh; + std::vector> m_mesh_objects; + std::string m_shape_name; + bool m_use_shape_name; + + std::unordered_map known_attr = { + {"Cd","color"}, + {"color","color"}, + {"transparency","alpha"}, + {"v","velocity"} + }; + + void add_arbitrary_geom_params(ICompoundProperty parent) + { + for (size_t i = 0; i < parent.getNumProperties(); ++i){ + const PropertyHeader &property_header = parent.getPropertyHeader(i); + + if (IFloatGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + + } + else if (IHalfGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + + } + else if (IDoubleGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + + } + else if (ICharGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + + } + else if (IInt16GeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IInt32GeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IInt64GeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IStringGeomParam::matches(property_header)){ + // shop_material path + process_arbitrary_geom_param( + parent, property_header); + } + else if (IV2fGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IV2dGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IV3fGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IV3dGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IN3fGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IN3dGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IC3hGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IC3fGeomParam::matches(property_header)){ + // e.g. vertex color attribute + process_arbitrary_geom_param( + parent, property_header); + } + else if (IC3cGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IP3fGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IP3dGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + else if (IBoolGeomParam::matches(property_header)){ + process_arbitrary_geom_param( + parent, property_header); + } + } + } + + template + void process_arbitrary_geom_param( + ICompoundProperty parent, + const PropertyHeader & property_header ) + { + std::string attr_name = property_header.getName(); + geom_param_t param(parent, property_header.getName()); + ISampleSelector sample_selector( m_alembic_time ); + typename geom_param_t::sample_type param_sample; + param.getExpanded(param_sample, sample_selector); + + // size, e.g. 3 for color; 1 for shop_materialpath + size_t extent = geom_param_t::prop_type::traits_type::dataType().getExtent(); + const pod_t *values = reinterpret_cast( + param_sample.getVals()->get()); + + switch (param_sample.getScope()) + { + case kVaryingScope: + case kVertexScope: + //Point attribute + if ( !(known_attr.find(attr_name) == known_attr.end()) ) + attr_name = "vertex_"+known_attr[attr_name]; + m_mesh->add_attribute(attr_name, extent, + FloatStorage::copy(values, m_vertex_count * extent)); + break; + case kFacevaryingScope:{ + // Vertex attribute + if ( !(known_attr.find(attr_name) == known_attr.end()) ) + attr_name = "vertex_"+known_attr[attr_name]; + + bool is_vertex_attr = attr_name.find("vertex_") == 0; + if(is_vertex_attr){ + m_mesh->add_attribute(attr_name, extent, + FloatStorage::copy(values, m_vertex_count * extent)); + } + else{ + Log(Info, "Skipping vertex attribute \"%s\" on \"%s\" as it does not start with \"vertex_\"", attr_name, m_mesh_name); + } + break; + } + case kConstantScope: + case kUnknownScope: + case kUniformScope:{ + // Known attribute + if ( !(known_attr.find(attr_name) == known_attr.end()) ) + attr_name = "face_"+known_attr[attr_name]; + + bool is_face_attr = attr_name.find("face_") == 0; + if(is_face_attr){ + m_mesh->add_attribute(attr_name, extent, + FloatStorage::copy(values, m_face_count * extent)); + } + else{ + Log(Info, "Skipping face attribute \"%s\" on \"%s\" as it does not start with \"face_\"", attr_name, m_mesh_name); + } + } + } + } + + template + void make_attr(geom_param_t param, + size_t num_polygons, + Int32ArraySamplePtr &vertices_per_face, + std::vector &data_array ) + { + typename geom_param_t::sample_type param_sample; + param.getExpanded(param_sample, ISampleSelector( m_alembic_time )); //abc time, for now take value at 0 time + size_t extent = geom_param_t::prop_type::traits_type::dataType().getExtent(); + ptr_t param_sample_values = param_sample.getVals(); + + size_t data_index=0; + for (size_t each_face=0; each_face data_per_face; + + for (size_t vertex_index=0; vertex_index& polymeshes) + { + for (size_t i = 0; i < obj.getNumChildren(); i++) + { + ScalarTransform4f child_transform = t; + IObject child = obj.getChild(i); + + if ( IXform::matches(child.getMetaData()) ){ + IXform xform_obj = IXform(child, kWrapExisting); + IXformSchema &xs = xform_obj.getSchema(); + XformSample xform_sample = xs.getValue(ISampleSelector(m_alembic_time)); + + ScalarTransform4f obj_xform = read_abc_xform(child); + if (xform_sample.getInheritsXforms()){ + child_transform = child_transform * obj_xform; + } + else{ + child_transform = obj_xform; + } + } + else if (IPolyMesh::matches(child.getMetaData())){ + bool mesh_visible = !m_load_visible || GetVisibilityProperty(child) == (bool)kVisibilityVisible; + bool add_mesh = true; + IPolyMesh poly_obj(child, kWrapExisting); + AlembicPolymesh polymesh; + polymesh.schema = poly_obj.getSchema(); + polymesh.id = child.getFullName(); + polymesh.xform = t; + + if (m_use_shape_name){ + // check if shape name is in alembic's mesh name to avoid + // typing full names. E.g. "sphere" instead of /sphere_object1/sphere1 + // if shape name from Xml file is not in mesh name, skip mesh + if (child.getFullName().find(m_shape_name) == std::string::npos) + add_mesh =false; + } + + if (mesh_visible && add_mesh) + polymeshes.push_back(polymesh); + + continue; + } + // continue traversing alembic tree for more child objects + find_children_recursively(child, sample_index, child_transform, polymeshes); + } + } + + ScalarTransform4f read_abc_xform(const IObject& obj) + { + ScalarTransform4f obj_xform; + if (IXform::matches(obj.getMetaData()) ){ + IXform xform_obj = IXform(obj, kWrapExisting); + IXformSchema &xs = xform_obj.getSchema(); + XformSample xform_sample = xs.getValue(ISampleSelector(m_alembic_time)); + + if (xform_sample.getInheritsXforms()){ + M44d m = xform_sample.getMatrix(); + for (size_t i = 0; i < 4; ++i){ + for (size_t j = 0; j < 4; ++j){ + obj_xform.matrix[i][j] = m[i][j]; + } + } + } + } + return obj_xform; + } + + MTS_DECLARE_CLASS() +}; + +MTS_IMPLEMENT_CLASS_VARIANT(AlembicMesh, Mesh) +MTS_EXPORT_PLUGIN(AlembicMesh, "Alembic Mesh") +NAMESPACE_END(mitsuba) From c5353804733c57fba27f5452e0e8cdc9c3671223 Mon Sep 17 00:00:00 2001 From: SergeyShlyaev <63272131+SergeyShlyaev@users.noreply.github.com> Date: Mon, 31 Aug 2020 22:16:32 -0400 Subject: [PATCH 3/6] support for loading multiple meshes Added support for loading multiple meshes from file Added support for `shape_name` to specify object by name --- src/shapes/abc.cpp | 218 ++++++++++++++++++++++++++++----------------- 1 file changed, 134 insertions(+), 84 deletions(-) diff --git a/src/shapes/abc.cpp b/src/shapes/abc.cpp index 3616b5ac1..155b685c6 100644 --- a/src/shapes/abc.cpp +++ b/src/shapes/abc.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include @@ -28,11 +27,16 @@ ABC (Alembic) mesh loader (:monosp:`abc`) * - filename - |string| - - Filename of the ABC file that should be loaded + - Filename of the Alembic file to load + * - shape_name + - |string| + - Alembic file may contain several separate meshes. This optional parameter + specifies name of mesh to load. (Default: not specified, i.e. load all objects) * - shape_index - |int| - - A :monosp:`.serialized` file may contain several separate meshes. This parameter - specifies which one should be loaded. (Default: 0, i.e. the first one) + - Alembic file may contain several separate meshes. This optional parameter + specifies which mesh should be loaded. (Default: 0, i.e. the first one) If both + shape_name and shape_index are specified, shape_name takes precedence. * - m_flip_normals - |bool| - Will flip normals on the mesh. Some programs output face indexes in clockwise order, some in counter-clockwise. @@ -70,22 +74,23 @@ using namespace Alembic::AbcCoreAbstract; template class AlembicMesh final : public Mesh { public: - MTS_IMPORT_BASE(Mesh, m_name, m_bbox, m_to_world, m_vertex_count, m_face_count, + MTS_IMPORT_BASE(Mesh, m_bsdf, m_name, m_bbox, m_to_world, m_vertex_count, m_face_count, m_vertex_positions_buf, m_vertex_normals_buf, m_vertex_texcoords_buf, - m_faces_buf, add_attribute, m_disable_vertex_normals, has_vertex_normals, + m_faces_buf, m_disable_vertex_normals, has_vertex_normals, has_vertex_texcoords, recompute_vertex_normals, set_children) - MTS_IMPORT_TYPES() + MTS_IMPORT_TYPES(BSDF, Shape) + + // Mesh is always stored in single precision + using InputFloat = float; + using InputPoint3f = Point; + using InputVector2f = Vector; + using InputVector3f = Vector; + using InputNormal3f = Normal; + using FloatStorage = DynamicBuffer>; - using typename Base::MeshAttributeType; using typename Base::ScalarSize; using typename Base::ScalarIndex; - using typename Base::InputFloat; - using typename Base::InputPoint3f; - using typename Base::InputVector2f; - using typename Base::InputVector3f; - using typename Base::InputNormal3f; - using typename Base::FloatStorage; - + using typename Base::MeshAttributeType; using ScalarIndex3 = std::array; struct AlembicPolymesh @@ -103,7 +108,6 @@ class AlembicMesh final : public Mesh { bool m_flip_normals = true; bool m_load_visible = false; - std::string m_mesh_name; // Alembics can store animation data // For now use values at 0 time until Mitsuba supports animaiton @@ -120,14 +124,24 @@ class AlembicMesh final : public Mesh { // Optionally fix handedness to avoid black renders. m_flip_normals = props.bool_("flip_normals", true); m_load_visible = props.bool_("load_visible", false); - const uint shape_index = props.int_("shape_index", 0); + const long shape_index = props.int_("shape_index", -1); const bool flip_tex_coords = props.bool_("flip_tex_coords", true); + m_shape_name = props.string("shape_name", ""); + m_use_shape_name = false; + bool use_shape_index = false; + + if (!m_shape_name.empty()) + m_use_shape_name = true; + + // Use shape_index if it is provided and shape_name is not used + if (shape_index != -1 && !m_use_shape_name) + use_shape_index = true; auto fail = [&](const std::string &descr) { - Throw("Error while loading Alembic file \"%s\": %s!", m_name, descr); + Throw("Error while loading Alembic file \"%s\": %s.", m_name, descr); }; auto detailed_fail = [&](const std::string &descr, std::string m_mesh_name="") { - Throw("Error while loading Alembic file \"%s\" at object \"%s\". %s!", m_name, m_mesh_name, descr); + Throw("Error while loading Alembic file \"%s\" at object \"%s\". %s.", m_name, m_mesh_name, descr); }; Log(Debug, "Loading mesh from \"%s\" ..", m_name); @@ -136,31 +150,41 @@ class AlembicMesh final : public Mesh { IFactory factory; IFactory::CoreType core_type; - IArchive archive = factory.getArchive(file_path.string(), core_type); + IArchive archive = factory.getArchive(file_path.string(), core_type); IObject archive_top = archive.getTop(); index_t sample_index = 0; - std::vector alembic_polymeshes; + std::vector alembic_polymeshes; ScalarTransform4f alembic_transform; - // how do we create multiple mesh shapes? - // for now create just one shape specified by shape_index - if (shape_index > (uint) archive_top.getNumChildren() - 1) + // create one mesh specified by shape_index + if (use_shape_index && (shape_index > (uint) archive_top.getNumChildren() - 1) ) fail(tfm::format("Unable to load mesh. Shape index \"%i\" is " "out of range 0..%i", shape_index, archive_top.getNumChildren() - 1)); - IObject object = archive_top.getChild(shape_index); - ScalarTransform4f obj_xform = read_abc_xform(object); - find_children_recursively(object, sample_index, alembic_transform, alembic_polymeshes); - - if (alembic_polymeshes.empty()) - fail("found no polygonal meshes in file."); + if (use_shape_index) + archive_top = archive_top.getChild(shape_index); + + ScalarTransform4f obj_xform = read_abc_xform(archive_top); + find_children_recursively(archive_top, sample_index, alembic_transform, alembic_polymeshes); + + if (alembic_polymeshes.empty()){ + std::string error_msg = "found no polygonal meshes in file"; + if (m_use_shape_name) + error_msg += tfm::format(". Got shape_name \"%s\" " + "please check if this shape was exported into Alembic file" , m_shape_name); + if (use_shape_index) + error_msg += tfm::format(". Got shape_index \"%i\" " + "could not get polymesh. Object at \"%i\" is \"%s\"" , + shape_index, shape_index, archive_top.getFullName()); + fail(error_msg); + } - for (size_t each_alembic = 0; each_alembic < alembic_polymeshes.size(); ++each_alembic) - { + for (size_t each_alembic = 0; each_alembic < alembic_polymeshes.size(); each_alembic++){ m_mesh_name = alembic_polymeshes[each_alembic].id; - IPolyMeshSchema poly_schema = alembic_polymeshes[each_alembic].schema; + + IPolyMeshSchema poly_schema = alembic_polymeshes[each_alembic].schema; IPolyMeshSchema::Sample sample = poly_schema.getValue(sample_index); IN3fGeomParam normals_param = poly_schema.getNormalsParam(); IV2fGeomParam uvs_param = poly_schema.getUVsParam(); @@ -197,7 +221,7 @@ class AlembicMesh final : public Mesh { ScalarIndex vertex_ctr = 0; if (polymesh_has_uvs) - { + { make_attr( uvs_param, num_polygons, vertices_per_face, texcoords); if (flip_tex_coords){ @@ -210,8 +234,8 @@ class AlembicMesh final : public Mesh { } } - if (!m_disable_vertex_normals && polymesh_has_vertex_normals) - { + if (!m_disable_vertex_normals && polymesh_has_vertex_normals) + { make_attr( normals_param, num_polygons, vertices_per_face, normals); @@ -220,11 +244,12 @@ class AlembicMesh final : public Mesh { InputNormal3f n = normals[i]; n = normalize(m_to_world.transform_affine(n)); if ( unlikely(!all(enoki::isfinite(n))) ){ - Log(Warn, "Invalid vertex normal: %s at vertex %s." , n, i ); + Log(Warn, + "Invalid vertex normal: %s at vertex %s of %s.", n, i, m_mesh_name); } normals[i] = n; } - } + } size_t uv_ctr=0; for (size_t each_face=0; each_face { m_vertex_count = vertex_ctr; m_face_count = (ScalarSize)indices.size(); - m_faces_buf = DynamicBuffer::copy(indices.data(), m_face_count * 3); m_vertex_positions_buf = empty(m_vertex_count * 3); + m_faces_buf = DynamicBuffer::copy(indices.data(), m_face_count * 3); + if (!m_disable_vertex_normals){ m_vertex_normals_buf = empty(m_vertex_count * 3); @@ -302,11 +328,6 @@ class AlembicMesh final : public Mesh { m_vertex_texcoords_buf = empty(m_vertex_count*2); } - m_faces_buf.managed(); - m_vertex_positions_buf.managed(); - m_vertex_normals_buf.managed(); - m_vertex_texcoords_buf.managed(); - for (const auto& v_ : vertex_map) { const VertexBinding *v = &v_; @@ -327,24 +348,45 @@ class AlembicMesh final : public Mesh { v = v->next; } } + + // Mesh constructor in mesh.cpp already has managed(), cuda_sync() + // and set_children() calls, so we don't need to call it here + m_mesh = new Mesh(m_mesh_name, m_vertex_count, m_face_count, + props, polymesh_has_vertex_normals, polymesh_has_uvs); - if (arbitrary_properties.valid()) - add_arbitrary_geom_params(arbitrary_properties); + m_mesh->vertex_positions_buffer() = m_vertex_positions_buf; + m_mesh->faces_buffer() = m_faces_buf; + m_mesh->vertex_normals_buffer() = m_vertex_normals_buf; + m_mesh->vertex_texcoords_buffer() = m_vertex_texcoords_buf; + m_mesh->recompute_bbox(); if (!m_disable_vertex_normals && normals.empty()) { Timer timer2; - recompute_vertex_normals(); + m_mesh->recompute_vertex_normals(); Log(Warn, "\"%s\": computed vertex normals for %s (took %s)", m_name, m_mesh_name, util::time_string(timer2.value())); } + + if (arbitrary_properties.valid()) + add_arbitrary_geom_params(arbitrary_properties); + + m_mesh_objects.push_back(m_mesh); } - if constexpr (is_cuda_array_v) - cuda_sync(); + } - set_children(); + std::vector> expand() const override { + return m_mesh_objects; } +protected: + std::string m_mesh_name; + private: + ref> m_mesh; + std::vector> m_mesh_objects; + std::string m_shape_name; + bool m_use_shape_name; + std::unordered_map known_attr = { {"Cd","color"}, {"color","color"}, @@ -453,11 +495,11 @@ class AlembicMesh final : public Mesh { { std::string attr_name = property_header.getName(); geom_param_t param(parent, property_header.getName()); - ISampleSelector sample_selector( m_alembic_time ); // alembic_time, for now just use value at 0 time + ISampleSelector sample_selector( m_alembic_time ); typename geom_param_t::sample_type param_sample; param.getExpanded(param_sample, sample_selector); - // 3 for color; 1 for shop_materialpath + // size, e.g. 3 for color; 1 for shop_materialpath size_t extent = geom_param_t::prop_type::traits_type::dataType().getExtent(); const pod_t *values = reinterpret_cast( param_sample.getVals()->get()); @@ -469,7 +511,8 @@ class AlembicMesh final : public Mesh { //Point attribute if ( !(known_attr.find(attr_name) == known_attr.end()) ) attr_name = "vertex_"+known_attr[attr_name]; - add_attribute(attr_name, extent, FloatStorage::copy(values, m_vertex_count * extent)); + m_mesh->add_attribute(attr_name, extent, + FloatStorage::copy(values, m_vertex_count * extent)); break; case kFacevaryingScope:{ // Vertex attribute @@ -478,7 +521,8 @@ class AlembicMesh final : public Mesh { bool is_vertex_attr = attr_name.find("vertex_") == 0; if(is_vertex_attr){ - add_attribute(attr_name, extent, FloatStorage::copy(values, m_vertex_count * extent)); + m_mesh->add_attribute(attr_name, extent, + FloatStorage::copy(values, m_vertex_count * extent)); } else{ Log(Info, "Skipping vertex attribute \"%s\" on \"%s\" as it does not start with \"vertex_\"", attr_name, m_mesh_name); @@ -494,7 +538,8 @@ class AlembicMesh final : public Mesh { bool is_face_attr = attr_name.find("face_") == 0; if(is_face_attr){ - add_attribute(attr_name, extent, FloatStorage::copy(values, m_face_count * extent)); + m_mesh->add_attribute(attr_name, extent, + FloatStorage::copy(values, m_face_count * extent)); } else{ Log(Info, "Skipping face attribute \"%s\" on \"%s\" as it does not start with \"face_\"", attr_name, m_mesh_name); @@ -521,7 +566,6 @@ class AlembicMesh final : public Mesh { std::vector data_per_face; for (size_t vertex_index=0; vertex_index { } void find_children_recursively(const IObject& obj, - index_t sample_index, - ScalarTransform4f &t, std::vector& map) - { - for (size_t i = 0; i < obj.getNumChildren(); i++) - { - ScalarTransform4f child_transform = t; - IObject child = obj.getChild(i); - - if ( IXform::matches(child.getMetaData()) ){ - IXform xform_obj = IXform(child, kWrapExisting); + index_t sample_index, + ScalarTransform4f &t, std::vector& polymeshes) + { + for (size_t i = 0; i < obj.getNumChildren(); i++) + { + ScalarTransform4f child_transform = t; + IObject child = obj.getChild(i); + + if ( IXform::matches(child.getMetaData()) ){ + IXform xform_obj = IXform(child, kWrapExisting); IXformSchema &xs = xform_obj.getSchema(); XformSample xform_sample = xs.getValue(ISampleSelector(m_alembic_time)); @@ -559,24 +603,32 @@ class AlembicMesh final : public Mesh { child_transform = obj_xform; } } - else if (IPolyMesh::matches(child.getMetaData())){ - IPolyMesh poly_obj(child, kWrapExisting); - AlembicPolymesh polymesh; - polymesh.schema = poly_obj.getSchema(); - polymesh.id = child.getFullName(); - polymesh.xform = t; - if (!m_load_visible || GetVisibilityProperty(child) == (bool)kVisibilityVisible){ - map.push_back(polymesh); + else if (IPolyMesh::matches(child.getMetaData())){ + bool mesh_visible = !m_load_visible || GetVisibilityProperty(child) == (bool)kVisibilityVisible; + bool add_mesh = true; + IPolyMesh poly_obj(child, kWrapExisting); + AlembicPolymesh polymesh; + polymesh.schema = poly_obj.getSchema(); + polymesh.id = child.getFullName(); + polymesh.xform = t; + + if (m_use_shape_name){ + // check if shape name is in alembic's mesh name to avoid + // typing full names. E.g. "sphere" instead of /sphere_object1/sphere1 + // if shape name from Xml file is not in mesh name, skip mesh + if (child.getFullName().find(m_shape_name) == std::string::npos) + add_mesh =false; } - else if (GetVisibilityProperty(child) == (bool)kVisibilityHidden){ - // Issue a message in the log for invisible object perhaps - } - continue; - } + + if (mesh_visible && add_mesh) + polymeshes.push_back(polymesh); + + continue; + } // continue traversing alembic tree for more child objects - find_children_recursively(child, sample_index, child_transform, map); - } - } + find_children_recursively(child, sample_index, child_transform, polymeshes); + } + } ScalarTransform4f read_abc_xform(const IObject& obj) { @@ -604,5 +656,3 @@ class AlembicMesh final : public Mesh { MTS_IMPLEMENT_CLASS_VARIANT(AlembicMesh, Mesh) MTS_EXPORT_PLUGIN(AlembicMesh, "Alembic Mesh") NAMESPACE_END(mitsuba) - - From 8b9c0fad9e9f3cf9f5de22f0c8720d698f940853 Mon Sep 17 00:00:00 2001 From: SergeyShlyaev <63272131+SergeyShlyaev@users.noreply.github.com> Date: Mon, 31 Aug 2020 22:22:14 -0400 Subject: [PATCH 4/6] Delete abc.cpp delete file accidentally placed in wrong place --- abc.cpp | 658 -------------------------------------------------------- 1 file changed, 658 deletions(-) delete mode 100644 abc.cpp diff --git a/abc.cpp b/abc.cpp deleted file mode 100644 index 155b685c6..000000000 --- a/abc.cpp +++ /dev/null @@ -1,658 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// Alembic Includes -#include -#include -#include -#include - -NAMESPACE_BEGIN(mitsuba) - -/**! - -.. _shape-abc: - -ABC (Alembic) mesh loader (:monosp:`abc`) ----------------------------------------------------------- - -.. pluginparameters:: - - * - filename - - |string| - - Filename of the Alembic file to load - * - shape_name - - |string| - - Alembic file may contain several separate meshes. This optional parameter - specifies name of mesh to load. (Default: not specified, i.e. load all objects) - * - shape_index - - |int| - - Alembic file may contain several separate meshes. This optional parameter - specifies which mesh should be loaded. (Default: 0, i.e. the first one) If both - shape_name and shape_index are specified, shape_name takes precedence. - * - m_flip_normals - - |bool| - - Will flip normals on the mesh. Some programs output face indexes in clockwise order, some in counter-clockwise. - This parameter optionally fixes handedness. (Default: |true|) - * - load_visible - - |bool| - - Alembic allows for objects or specific faces of an object to be flagged as either visible or hidden. - This is useful because it allows objects or faces to appear or disaper over time, by animating the visiblity property. - Setting this to |true| will load only objects flagged as visible. (Default: |false|) - * - face_normals - - |bool| - - When set to |true|, any existing or computed vertex normals are - discarded and *face normals* will instead be used during rendering. - This gives the rendered object a faceted appearance. (Default: |false|) - * - flip_tex_coords - - |bool| - - Treat the vertical component of the texture as inverted? (Default: |true|) - * - to_world - - |Transform4f| - - Specifies an optional linear object-to-world Transform4fation. - (Default: none, i.e. object space = world space) - -This plugin implements loader for Alembic file format (www.alembic.io). The -current plugin implementation supports arbitrary meshes with optional UV -coordinates, vertex normals and other custom vertex or face attributes. - - */ - - -using namespace std; -using namespace Alembic::AbcGeom; -using namespace Alembic::AbcCoreFactory; -using namespace Alembic::AbcCoreAbstract; - -template -class AlembicMesh final : public Mesh { -public: - MTS_IMPORT_BASE(Mesh, m_bsdf, m_name, m_bbox, m_to_world, m_vertex_count, m_face_count, - m_vertex_positions_buf, m_vertex_normals_buf, m_vertex_texcoords_buf, - m_faces_buf, m_disable_vertex_normals, has_vertex_normals, - has_vertex_texcoords, recompute_vertex_normals, set_children) - MTS_IMPORT_TYPES(BSDF, Shape) - - // Mesh is always stored in single precision - using InputFloat = float; - using InputPoint3f = Point; - using InputVector2f = Vector; - using InputVector3f = Vector; - using InputNormal3f = Normal; - using FloatStorage = DynamicBuffer>; - - using typename Base::ScalarSize; - using typename Base::ScalarIndex; - using typename Base::MeshAttributeType; - using ScalarIndex3 = std::array; - - struct AlembicPolymesh - { - std::string id; - IPolyMeshSchema schema; - ScalarTransform4f xform; - }; - - struct VertexBinding { - ScalarIndex3 key {{ 0, 0, 0 }}; - ScalarIndex value { 0 }; - VertexBinding *next { nullptr }; - }; - - bool m_flip_normals = true; - bool m_load_visible = false; - - // Alembics can store animation data - // For now use values at 0 time until Mitsuba supports animaiton - double m_alembic_time = 0; - - AlembicMesh(const Properties &props) : Base(props) { - - auto fs = Thread::thread()->file_resolver(); - fs::path file_path = fs->resolve(props.string("filename")); - m_name = file_path.filename().string(); - - // Some DCC output face indices in clockwise order, some in counter-clockwise. - // It looks like Mitsuba expects opposite order compared to most common DCC's - // Optionally fix handedness to avoid black renders. - m_flip_normals = props.bool_("flip_normals", true); - m_load_visible = props.bool_("load_visible", false); - const long shape_index = props.int_("shape_index", -1); - const bool flip_tex_coords = props.bool_("flip_tex_coords", true); - m_shape_name = props.string("shape_name", ""); - m_use_shape_name = false; - bool use_shape_index = false; - - if (!m_shape_name.empty()) - m_use_shape_name = true; - - // Use shape_index if it is provided and shape_name is not used - if (shape_index != -1 && !m_use_shape_name) - use_shape_index = true; - - auto fail = [&](const std::string &descr) { - Throw("Error while loading Alembic file \"%s\": %s.", m_name, descr); - }; - auto detailed_fail = [&](const std::string &descr, std::string m_mesh_name="") { - Throw("Error while loading Alembic file \"%s\" at object \"%s\". %s.", m_name, m_mesh_name, descr); - }; - - Log(Debug, "Loading mesh from \"%s\" ..", m_name); - if (!fs::exists(file_path)) - fail("file not found"); - - IFactory factory; - IFactory::CoreType core_type; - IArchive archive = factory.getArchive(file_path.string(), core_type); - IObject archive_top = archive.getTop(); - - index_t sample_index = 0; - std::vector alembic_polymeshes; - ScalarTransform4f alembic_transform; - - // create one mesh specified by shape_index - if (use_shape_index && (shape_index > (uint) archive_top.getNumChildren() - 1) ) - fail(tfm::format("Unable to load mesh. Shape index \"%i\" is " - "out of range 0..%i", - shape_index, archive_top.getNumChildren() - 1)); - - if (use_shape_index) - archive_top = archive_top.getChild(shape_index); - - ScalarTransform4f obj_xform = read_abc_xform(archive_top); - find_children_recursively(archive_top, sample_index, alembic_transform, alembic_polymeshes); - - if (alembic_polymeshes.empty()){ - std::string error_msg = "found no polygonal meshes in file"; - if (m_use_shape_name) - error_msg += tfm::format(". Got shape_name \"%s\" " - "please check if this shape was exported into Alembic file" , m_shape_name); - if (use_shape_index) - error_msg += tfm::format(". Got shape_index \"%i\" " - "could not get polymesh. Object at \"%i\" is \"%s\"" , - shape_index, shape_index, archive_top.getFullName()); - fail(error_msg); - } - - for (size_t each_alembic = 0; each_alembic < alembic_polymeshes.size(); each_alembic++){ - m_mesh_name = alembic_polymeshes[each_alembic].id; - - IPolyMeshSchema poly_schema = alembic_polymeshes[each_alembic].schema; - IPolyMeshSchema::Sample sample = poly_schema.getValue(sample_index); - IN3fGeomParam normals_param = poly_schema.getNormalsParam(); - IV2fGeomParam uvs_param = poly_schema.getUVsParam(); - ICompoundProperty arbitrary_properties = poly_schema.getArbGeomParams(); - alembic_polymeshes[each_alembic].xform = obj_xform * alembic_polymeshes[each_alembic].xform; - - const bool polymesh_has_uvs = uvs_param.valid() && uvs_param.getValueProperty().valid(); - const bool polymesh_has_vertex_normals = normals_param.valid() && normals_param.getValueProperty().valid(); - - size_t num_polygons=sample.getFaceCounts()->size(); - P3fArraySamplePtr positions = sample.getPositions(); - - std::vector vertices; - std::vector normals; - std::vector texcoords; - std::vector vertex_map; - - for (size_t i = 0, e = positions->size(); i < e; ++i){ - InputPoint3f p = alembic_polymeshes[each_alembic].xform.transform_affine( - InputPoint3f( (*positions)[i][0], (*positions)[i][1], (*positions)[i][2] ) ); - p = m_to_world.transform_affine(p); - if (unlikely(!all(enoki::isfinite(p)))) - detailed_fail("Alembic mesh contains invalid vertex position data", m_mesh_name); - m_bbox.expand(p); - vertices.push_back(p); - } - - Int32ArraySamplePtr alembic_face_indices = sample.getFaceIndices(); - std::vector indices; - indices.reserve( alembic_face_indices->size() ); - - Int32ArraySamplePtr vertices_per_face = sample.getFaceCounts(); - size_t mesh_face_index=0; - ScalarIndex vertex_ctr = 0; - - if (polymesh_has_uvs) - { - make_attr( - uvs_param, num_polygons, vertices_per_face, texcoords); - if (flip_tex_coords){ - for (size_t i = 0; i < texcoords.size(); i++) - { - InputVector2f uv = texcoords[i]; - uv.y() = 1.f - uv.y(); - texcoords[i] = uv; - } - } - } - - if (!m_disable_vertex_normals && polymesh_has_vertex_normals) - { - make_attr( - normals_param, num_polygons, vertices_per_face, normals); - - for (size_t i = 0; i < normals.size(); i++) - { - InputNormal3f n = normals[i]; - n = normalize(m_to_world.transform_affine(n)); - if ( unlikely(!all(enoki::isfinite(n))) ){ - Log(Warn, - "Invalid vertex normal: %s at vertex %s of %s.", n, i, m_mesh_name); - } - normals[i] = n; - } - } - - size_t uv_ctr=0; - for (size_t each_face=0; each_facecurrent_face_indices; - current_face_indices.reserve( vertex_count_in_face ); - std::vector uvs_per_face; - for (size_t vertex_index = 0; vertex_indexkey != key && entry->next != nullptr) - entry = entry->next; - - ScalarIndex id; - if (entry->key == key) { - // Hit - id = entry->value; - } else { - // Miss - if (entry->key != ScalarIndex3{{0, 0, 0}}) { - entry->next = new VertexBinding(); - entry = entry->next; - } - entry->key = key; - id = entry->value = vertex_ctr++; - } - - if (vertex_index < 3) { - tri[vertex_index] = id; - } else { - tri[1] = tri[2]; - tri[2] = id; - } - vertex_index++; - if (vertex_index >= 3){ - indices.push_back(tri); - } - uv_ctr++; - } - } - - m_vertex_count = vertex_ctr; - m_face_count = (ScalarSize)indices.size(); - m_vertex_positions_buf = empty(m_vertex_count * 3); - m_faces_buf = DynamicBuffer::copy(indices.data(), m_face_count * 3); - - - if (!m_disable_vertex_normals){ - m_vertex_normals_buf = empty(m_vertex_count * 3); - } - - if (!texcoords.empty()){ - m_vertex_texcoords_buf = empty(m_vertex_count*2); - } - - for (const auto& v_ : vertex_map) { - const VertexBinding *v = &v_; - - while (v && v->key != ScalarIndex3{{0, 0, 0}}) { - InputFloat* position_ptr = m_vertex_positions_buf.data() + v->value * 3; - InputFloat* normal_ptr = m_vertex_normals_buf.data() + v->value * 3; - InputFloat* texcoord_ptr = m_vertex_texcoords_buf.data() + v->value * 2; - auto key = v->key; - - store_unaligned(position_ptr, vertices[key[0]]); - - if (key[1] && polymesh_has_uvs) - store_unaligned(texcoord_ptr, texcoords[key[1] - 1]); - - if (!m_disable_vertex_normals && polymesh_has_vertex_normals && key[1]) - store_unaligned(normal_ptr, normals[key[1] - 1]); - - v = v->next; - } - } - - // Mesh constructor in mesh.cpp already has managed(), cuda_sync() - // and set_children() calls, so we don't need to call it here - m_mesh = new Mesh(m_mesh_name, m_vertex_count, m_face_count, - props, polymesh_has_vertex_normals, polymesh_has_uvs); - - m_mesh->vertex_positions_buffer() = m_vertex_positions_buf; - m_mesh->faces_buffer() = m_faces_buf; - m_mesh->vertex_normals_buffer() = m_vertex_normals_buf; - m_mesh->vertex_texcoords_buffer() = m_vertex_texcoords_buf; - - m_mesh->recompute_bbox(); - if (!m_disable_vertex_normals && normals.empty()) { - Timer timer2; - m_mesh->recompute_vertex_normals(); - Log(Warn, "\"%s\": computed vertex normals for %s (took %s)", m_name, m_mesh_name, - util::time_string(timer2.value())); - } - - if (arbitrary_properties.valid()) - add_arbitrary_geom_params(arbitrary_properties); - - m_mesh_objects.push_back(m_mesh); - } - } - - std::vector> expand() const override { - return m_mesh_objects; - } - -protected: - std::string m_mesh_name; - -private: - ref> m_mesh; - std::vector> m_mesh_objects; - std::string m_shape_name; - bool m_use_shape_name; - - std::unordered_map known_attr = { - {"Cd","color"}, - {"color","color"}, - {"transparency","alpha"}, - {"v","velocity"} - }; - - void add_arbitrary_geom_params(ICompoundProperty parent) - { - for (size_t i = 0; i < parent.getNumProperties(); ++i){ - const PropertyHeader &property_header = parent.getPropertyHeader(i); - - if (IFloatGeomParam::matches(property_header)){ - process_arbitrary_geom_param( - parent, property_header); - - } - else if (IHalfGeomParam::matches(property_header)){ - process_arbitrary_geom_param( - parent, property_header); - - } - else if (IDoubleGeomParam::matches(property_header)){ - process_arbitrary_geom_param( - parent, property_header); - - } - else if (ICharGeomParam::matches(property_header)){ - process_arbitrary_geom_param( - parent, property_header); - - } - else if (IInt16GeomParam::matches(property_header)){ - process_arbitrary_geom_param( - parent, property_header); - } - else if (IInt32GeomParam::matches(property_header)){ - process_arbitrary_geom_param( - parent, property_header); - } - else if (IInt64GeomParam::matches(property_header)){ - process_arbitrary_geom_param( - parent, property_header); - } - else if (IStringGeomParam::matches(property_header)){ - // shop_material path - process_arbitrary_geom_param( - parent, property_header); - } - else if (IV2fGeomParam::matches(property_header)){ - process_arbitrary_geom_param( - parent, property_header); - } - else if (IV2dGeomParam::matches(property_header)){ - process_arbitrary_geom_param( - parent, property_header); - } - else if (IV3fGeomParam::matches(property_header)){ - process_arbitrary_geom_param( - parent, property_header); - } - else if (IV3dGeomParam::matches(property_header)){ - process_arbitrary_geom_param( - parent, property_header); - } - else if (IN3fGeomParam::matches(property_header)){ - process_arbitrary_geom_param( - parent, property_header); - } - else if (IN3dGeomParam::matches(property_header)){ - process_arbitrary_geom_param( - parent, property_header); - } - else if (IC3hGeomParam::matches(property_header)){ - process_arbitrary_geom_param( - parent, property_header); - } - else if (IC3fGeomParam::matches(property_header)){ - // e.g. vertex color attribute - process_arbitrary_geom_param( - parent, property_header); - } - else if (IC3cGeomParam::matches(property_header)){ - process_arbitrary_geom_param( - parent, property_header); - } - else if (IP3fGeomParam::matches(property_header)){ - process_arbitrary_geom_param( - parent, property_header); - } - else if (IP3dGeomParam::matches(property_header)){ - process_arbitrary_geom_param( - parent, property_header); - } - else if (IBoolGeomParam::matches(property_header)){ - process_arbitrary_geom_param( - parent, property_header); - } - } - } - - template - void process_arbitrary_geom_param( - ICompoundProperty parent, - const PropertyHeader & property_header ) - { - std::string attr_name = property_header.getName(); - geom_param_t param(parent, property_header.getName()); - ISampleSelector sample_selector( m_alembic_time ); - typename geom_param_t::sample_type param_sample; - param.getExpanded(param_sample, sample_selector); - - // size, e.g. 3 for color; 1 for shop_materialpath - size_t extent = geom_param_t::prop_type::traits_type::dataType().getExtent(); - const pod_t *values = reinterpret_cast( - param_sample.getVals()->get()); - - switch (param_sample.getScope()) - { - case kVaryingScope: - case kVertexScope: - //Point attribute - if ( !(known_attr.find(attr_name) == known_attr.end()) ) - attr_name = "vertex_"+known_attr[attr_name]; - m_mesh->add_attribute(attr_name, extent, - FloatStorage::copy(values, m_vertex_count * extent)); - break; - case kFacevaryingScope:{ - // Vertex attribute - if ( !(known_attr.find(attr_name) == known_attr.end()) ) - attr_name = "vertex_"+known_attr[attr_name]; - - bool is_vertex_attr = attr_name.find("vertex_") == 0; - if(is_vertex_attr){ - m_mesh->add_attribute(attr_name, extent, - FloatStorage::copy(values, m_vertex_count * extent)); - } - else{ - Log(Info, "Skipping vertex attribute \"%s\" on \"%s\" as it does not start with \"vertex_\"", attr_name, m_mesh_name); - } - break; - } - case kConstantScope: - case kUnknownScope: - case kUniformScope:{ - // Known attribute - if ( !(known_attr.find(attr_name) == known_attr.end()) ) - attr_name = "face_"+known_attr[attr_name]; - - bool is_face_attr = attr_name.find("face_") == 0; - if(is_face_attr){ - m_mesh->add_attribute(attr_name, extent, - FloatStorage::copy(values, m_face_count * extent)); - } - else{ - Log(Info, "Skipping face attribute \"%s\" on \"%s\" as it does not start with \"face_\"", attr_name, m_mesh_name); - } - } - } - } - - template - void make_attr(geom_param_t param, - size_t num_polygons, - Int32ArraySamplePtr &vertices_per_face, - std::vector &data_array ) - { - typename geom_param_t::sample_type param_sample; - param.getExpanded(param_sample, ISampleSelector( m_alembic_time )); //abc time, for now take value at 0 time - size_t extent = geom_param_t::prop_type::traits_type::dataType().getExtent(); - ptr_t param_sample_values = param_sample.getVals(); - - size_t data_index=0; - for (size_t each_face=0; each_face data_per_face; - - for (size_t vertex_index=0; vertex_index& polymeshes) - { - for (size_t i = 0; i < obj.getNumChildren(); i++) - { - ScalarTransform4f child_transform = t; - IObject child = obj.getChild(i); - - if ( IXform::matches(child.getMetaData()) ){ - IXform xform_obj = IXform(child, kWrapExisting); - IXformSchema &xs = xform_obj.getSchema(); - XformSample xform_sample = xs.getValue(ISampleSelector(m_alembic_time)); - - ScalarTransform4f obj_xform = read_abc_xform(child); - if (xform_sample.getInheritsXforms()){ - child_transform = child_transform * obj_xform; - } - else{ - child_transform = obj_xform; - } - } - else if (IPolyMesh::matches(child.getMetaData())){ - bool mesh_visible = !m_load_visible || GetVisibilityProperty(child) == (bool)kVisibilityVisible; - bool add_mesh = true; - IPolyMesh poly_obj(child, kWrapExisting); - AlembicPolymesh polymesh; - polymesh.schema = poly_obj.getSchema(); - polymesh.id = child.getFullName(); - polymesh.xform = t; - - if (m_use_shape_name){ - // check if shape name is in alembic's mesh name to avoid - // typing full names. E.g. "sphere" instead of /sphere_object1/sphere1 - // if shape name from Xml file is not in mesh name, skip mesh - if (child.getFullName().find(m_shape_name) == std::string::npos) - add_mesh =false; - } - - if (mesh_visible && add_mesh) - polymeshes.push_back(polymesh); - - continue; - } - // continue traversing alembic tree for more child objects - find_children_recursively(child, sample_index, child_transform, polymeshes); - } - } - - ScalarTransform4f read_abc_xform(const IObject& obj) - { - ScalarTransform4f obj_xform; - if (IXform::matches(obj.getMetaData()) ){ - IXform xform_obj = IXform(obj, kWrapExisting); - IXformSchema &xs = xform_obj.getSchema(); - XformSample xform_sample = xs.getValue(ISampleSelector(m_alembic_time)); - - if (xform_sample.getInheritsXforms()){ - M44d m = xform_sample.getMatrix(); - for (size_t i = 0; i < 4; ++i){ - for (size_t j = 0; j < 4; ++j){ - obj_xform.matrix[i][j] = m[i][j]; - } - } - } - } - return obj_xform; - } - - MTS_DECLARE_CLASS() -}; - -MTS_IMPLEMENT_CLASS_VARIANT(AlembicMesh, Mesh) -MTS_EXPORT_PLUGIN(AlembicMesh, "Alembic Mesh") -NAMESPACE_END(mitsuba) From e10bad021fab8ec7d5c588ffa0d7dae349268890 Mon Sep 17 00:00:00 2001 From: SergeyShlyaev <63272131+SergeyShlyaev@users.noreply.github.com> Date: Sun, 6 Sep 2020 17:45:09 -0400 Subject: [PATCH 5/6] Alembic camera --- src/sensors/CMakeLists.txt | 1 + src/sensors/perspective_abc.cpp | 416 ++++++++++++++++++++++++++++++++ 2 files changed, 417 insertions(+) create mode 100644 src/sensors/perspective_abc.cpp diff --git a/src/sensors/CMakeLists.txt b/src/sensors/CMakeLists.txt index 6c643a55c..65cdf13e0 100644 --- a/src/sensors/CMakeLists.txt +++ b/src/sensors/CMakeLists.txt @@ -1,6 +1,7 @@ set(MTS_PLUGIN_PREFIX "sensors") add_plugin(perspective perspective.cpp) +add_plugin(perspective_abc perspective_abc.cpp) add_plugin(radiancemeter radiancemeter.cpp) add_plugin(thinlens thinlens.cpp) add_plugin(irradiancemeter irradiancemeter.cpp) diff --git a/src/sensors/perspective_abc.cpp b/src/sensors/perspective_abc.cpp new file mode 100644 index 000000000..6b551ff5d --- /dev/null +++ b/src/sensors/perspective_abc.cpp @@ -0,0 +1,416 @@ +#include +#include +#include +#include +#include + +#define show(x) std::cout <<"\033[1m\033[34m"<< "Camera Reading: " << x <<"\033[0m"<< std::endl; +#define shwo(x) std::cout <<"\033[1m\033[34m"<< "Camera Reading: " << x <<"\033[0m"<< std::endl; +#define show2(x, y) std::cout <<"\033[1m\033[34m"<< x << y <<"\033[0m"<< std::endl; + + +// Alembic Includes +#include +#include +#include +#include + +using namespace Alembic::AbcGeom; +using namespace Alembic::AbcCoreFactory; +using namespace Alembic::AbcCoreAbstract; + +NAMESPACE_BEGIN(mitsuba) + +/**! + +.. _sensor-perspective_abc: + +Alembic loader for perspective pinhole camera (:monosp:`perspective_abc`) +-------------------------------------------------- + +.. pluginparameters:: + + * - filename + - |string| + - Filename of the Alembic file to load + * - shape_name + - |string| + - Alembic file may contain several separate cameras or other objects. This optional parameter + specifies name of camera to load. + * - to_world + - |transform| + - Specifies an optional camera-to-world transformation. + (Default: none (i.e. camera space = world space)) + +This plugin brings support for reading cameras from Alembic file. +It reads position and orientation data as well as field of view and near/far clipping planes. +Other camera parameters are identical to standard perspective camera plugin. + +.. code-block:: xml + + + + + + + */ + +template +class AlembicPerspectiveCamera final : public ProjectiveCamera { +public: + MTS_IMPORT_BASE(ProjectiveCamera, m_world_transform, m_needs_sample_3, + m_film, m_sampler, m_resolution, m_shutter_open, + m_shutter_open_time, m_near_clip, m_far_clip) + MTS_IMPORT_TYPES() + + // ============================================================= + //! @{ \name Constructors + // ============================================================= + + AlembicPerspectiveCamera(const Properties &props) : Base(props) { + auto fs = Thread::thread()->file_resolver(); + fs::path file_path = fs->resolve(props.string("filename")); + m_name = file_path.filename().string(); + + auto fail = [&](const std::string &descr) { + Throw("Error while loading camera from Alembic file \"%s\": %s.", m_name, descr); + }; + + Log(Debug, "Loading camera from \"%s\" ..", m_name); + if (!fs::exists(file_path)) + fail("file not found"); + + m_shape_name = props.string("shape_name", ""); + const bool convert_to_z_up = props.bool_("convert_to_z_up", true); + m_use_shape_name = false; + + if (!m_shape_name.empty()) + m_use_shape_name = true; + + IFactory factory; + IFactory::CoreType core_type; + IArchive archive = factory.getArchive(file_path.string(), core_type); + IObject archive_top = archive.getTop(); + + index_t sample_index = 0; + ScalarTransform4f alembic_transform; + + ICamera camera; + find_camera_recursively(archive_top, sample_index, alembic_transform, camera); + + if (!camera.valid()){ + std::string error_msg = "did not found camera in file"; + if (m_use_shape_name) + error_msg += tfm::format(". Got shape_name \"%s\". " + "Please check if this camera was exported into " + "Alembic file and name is correct" , m_shape_name); + fail(error_msg); + } + + CameraSample camera_sample = camera.getSchema().getValue( + ISampleSelector(m_alembic_time) ); + + ScalarVector2i size = m_film->size(); + m_x_fov = camera_sample.getFieldOfView(); + + //support crops + parse_fov(props, size.x() / (float) size.y()); + + // convert y up to z up if requested + if (convert_to_z_up){ + ScalarTransform4f rotate_to_z_up; + rotate_to_z_up.matrix = {-1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, -1, 0, + 0, 0, 0, 1 + }; + m_obj_xform = m_obj_xform * rotate_to_z_up; + } + + m_near_clip = camera_sample.getNearClippingPlane(); + m_far_clip = camera_sample.getFarClippingPlane(); + + if (m_world_transform->has_scale()) + Throw("Scale factors in the camera-to-world transformation are not allowed!"); + + update_camera_transforms(); + } + + void update_camera_transforms() { + m_camera_to_sample = perspective_projection( + m_film->size(), m_film->crop_size(), m_film->crop_offset(), + m_x_fov, m_near_clip, m_far_clip); + + m_sample_to_camera = m_camera_to_sample.inverse(); + + // Position differentials on the near plane + m_dx = m_sample_to_camera * ScalarPoint3f(1.f / m_resolution.x(), 0.f, 0.f) - + m_sample_to_camera * ScalarPoint3f(0.f); + m_dy = m_sample_to_camera * ScalarPoint3f(0.f, 1.f / m_resolution.y(), 0.f) + - m_sample_to_camera * ScalarPoint3f(0.f); + + /* Precompute some data for importance(). Please + look at that function for further details. */ + ScalarPoint3f pmin(m_sample_to_camera * ScalarPoint3f(0.f, 0.f, 0.f)), + pmax(m_sample_to_camera * ScalarPoint3f(1.f, 1.f, 0.f)); + + m_image_rect.reset(); + m_image_rect.expand(ScalarPoint2f(pmin.x(), pmin.y()) / pmin.z()); + m_image_rect.expand(ScalarPoint2f(pmax.x(), pmax.y()) / pmax.z()); + m_normalization = 1.f / m_image_rect.volume(); + m_needs_sample_3 = false; + } + + //! @} + // ============================================================= + + // ============================================================= + //! @{ \name Sampling methods (Sensor interface) + // ============================================================= + + std::pair sample_ray(Float time, Float wavelength_sample, + const Point2f &position_sample, + const Point2f & /*aperture_sample*/, + Mask active) const override { + MTS_MASKED_FUNCTION(ProfilerPhase::EndpointSampleRay, active); + + auto [wavelengths, wav_weight] = sample_wavelength(wavelength_sample); + Ray3f ray; + ray.time = time; + ray.wavelengths = wavelengths; + + // Compute the sample position on the near plane (local camera space). + Point3f near_p = m_sample_to_camera * + Point3f(position_sample.x(), position_sample.y(), 0.f); + + // Convert into a normalized ray direction; adjust the ray interval accordingly. + Vector3f d = normalize(Vector3f(near_p)); + + Float inv_z = rcp(d.z()); + ray.mint = m_near_clip * inv_z; + ray.maxt = m_far_clip * inv_z; + + auto trafo = m_obj_xform * m_world_transform->eval(ray.time, active); + // auto trafo = m_obj_xform; + + ray.o = trafo.translation(); + ray.d = trafo * d; + ray.update(); + + return std::make_pair(ray, wav_weight); + } + + std::pair + sample_ray_differential(Float time, Float wavelength_sample, const Point2f &position_sample, + const Point2f & /*aperture_sample*/, Mask active) const override { + MTS_MASKED_FUNCTION(ProfilerPhase::EndpointSampleRay, active); + + auto [wavelengths, wav_weight] = sample_wavelength(wavelength_sample); + RayDifferential3f ray; + ray.time = time; + ray.wavelengths = wavelengths; + + // Compute the sample position on the near plane (local camera space). + Point3f near_p = m_sample_to_camera * + Point3f(position_sample.x(), position_sample.y(), 0.f); + + // Convert into a normalized ray direction; adjust the ray interval accordingly. + Vector3f d = normalize(Vector3f(near_p)); + Float inv_z = rcp(d.z()); + ray.mint = m_near_clip * inv_z; + ray.maxt = m_far_clip * inv_z; + + auto trafo = m_obj_xform * m_world_transform->eval(ray.time, active); + // auto trafo = m_obj_xform; + ray.o = trafo.transform_affine(Point3f(0.f)); + ray.d = trafo * d; + ray.update(); + + ray.o_x = ray.o_y = ray.o; + + ray.d_x = trafo * normalize(Vector3f(near_p) + m_dx); + ray.d_y = trafo * normalize(Vector3f(near_p) + m_dy); + ray.has_differentials = true; + + return std::make_pair(ray, wav_weight); + } + + ScalarBoundingBox3f bbox() const override { + return m_world_transform->translation_bounds(); + } + + /** + * \brief Compute the directional sensor response function of the camera + * multiplied with the cosine foreshortening factor associated with the + * image plane + * + * \param d + * A normalized direction vector from the aperture position to the + * reference point in question (all in local camera space) + */ + Float importance(const Vector3f &d) const { + /* How is this derived? Imagine a hypothetical image plane at a + distance of d=1 away from the pinhole in camera space. + + Then the visible rectangular portion of the plane has the area + + A = (2 * tan(0.5 * xfov in radians))^2 / aspect + + Since we allow crop regions, the actual visible area is + potentially reduced: + + A' = A * (cropX / filmX) * (cropY / filmY) + + Perspective transformations of such aligned rectangles produce + an equivalent scaled (but otherwise undistorted) rectangle + in screen space. This means that a strategy, which uniformly + generates samples in screen space has an associated area + density of 1/A' on this rectangle. + + To compute the solid angle density of a sampled point P on + the rectangle, we can apply the usual measure conversion term: + + d_omega = 1/A' * distance(P, origin)^2 / cos(theta) + + where theta is the angle that the unit direction vector from + the origin to P makes with the rectangle. Since + + distance(P, origin)^2 = Px^2 + Py^2 + 1 + + and + + cos(theta) = 1/sqrt(Px^2 + Py^2 + 1), + + we have + + d_omega = 1 / (A' * cos^3(theta)) + */ + + Float ct = Frame3f::cos_theta(d), inv_ct = rcp(ct); + + // Compute the position on the plane at distance 1 + Point2f p(d.x() * inv_ct, d.y() * inv_ct); + + /* Check if the point lies to the front and inside the + chosen crop rectangle */ + Mask valid = ct > 0 && m_image_rect.contains(p); + + return select(valid, m_normalization * inv_ct * inv_ct * inv_ct, 0.f); + } + + //! @} + // ============================================================= + + void traverse(TraversalCallback *callback) override { + Base::traverse(callback); + // TODO x_fov + } + + void parameters_changed(const std::vector &keys) override { + Base::parameters_changed(keys); + // TODO + } + + std::string to_string() const override { + using string::indent; + + std::ostringstream oss; + oss << "AlembicPerspectiveCamera[" << std::endl + << " x_fov = " << m_x_fov << "," << std::endl + << " near_clip = " << m_near_clip << "," << std::endl + << " far_clip = " << m_far_clip << "," << std::endl + << " film = " << indent(m_film) << "," << std::endl + << " sampler = " << indent(m_sampler) << "," << std::endl + << " resolution = " << m_resolution << "," << std::endl + << " shutter_open = " << m_shutter_open << "," << std::endl + << " shutter_open_time = " << m_shutter_open_time << "," << std::endl + << " world_transform = " << indent(m_world_transform) << std::endl + << "]"; + return oss.str(); + } + + MTS_DECLARE_CLASS() +private: + ScalarTransform4f m_camera_to_sample; + ScalarTransform4f m_sample_to_camera; + ScalarBoundingBox2f m_image_rect; + ScalarFloat m_normalization; + ScalarFloat m_x_fov; + ScalarVector3f m_dx, m_dy; + + // Alembic camera variables + std::string m_alembic_name; + double m_alembic_time = 0; + std::string m_name; + Transform4f m_obj_xform; + std::string m_shape_name; + bool m_use_shape_name; + + void find_camera_recursively(const IObject& obj, + index_t sample_index, + ScalarTransform4f &t, ICamera& camera ) + { + for (size_t i = 0; i < obj.getNumChildren(); i++) + { + ScalarTransform4f child_transform = t; + IObject child = obj.getChild(i); + + if ( IXform::matches(child.getMetaData()) ){ + IXform xform_obj = IXform(child, kWrapExisting); + IXformSchema &xs = xform_obj.getSchema(); + XformSample xform_sample = xs.getValue(ISampleSelector(m_alembic_time)); + + ScalarTransform4f obj_xform = read_abc_xform(child); + if (xform_sample.getInheritsXforms()){ + // camera can be constrained to another object + child_transform = child_transform * obj_xform; + } + else{ + child_transform = obj_xform; + } + } + else if ( ICamera::matches(child.getMetaData()) ){ + bool add_camera = true; + if (m_use_shape_name){ + // check if shape name is in alembic's camera name to avoid + // typing full names. + // if camera name from Xml file is not in camera name, skip camera + if (child.getFullName().find(m_shape_name) == std::string::npos) + add_camera =false; + } + + if (add_camera){ + camera = ICamera(child, kWrapExisting); + m_obj_xform = child_transform; + } + continue; + } + // continue traversing alembic tree in case there are more cameras + find_camera_recursively(child, sample_index, child_transform, camera); + } + } + + ScalarTransform4f read_abc_xform(const IObject& obj) + { + ScalarTransform4f obj_xform; + if (IXform::matches(obj.getMetaData()) ){ + IXform xform_obj = IXform(obj, kWrapExisting); + IXformSchema &xs = xform_obj.getSchema(); + XformSample xform_sample = xs.getValue(ISampleSelector(m_alembic_time)); + + if (xform_sample.getInheritsXforms()){ + M44d m = xform_sample.getMatrix(); + for (size_t i = 0; i < 4; ++i){ + for (size_t j = 0; j < 4; ++j){ + obj_xform.matrix[i][j] = m[i][j]; + } + } + } + } + return obj_xform; + } +}; + +MTS_IMPLEMENT_CLASS_VARIANT(AlembicPerspectiveCamera, ProjectiveCamera) +MTS_EXPORT_PLUGIN(AlembicPerspectiveCamera, "Alembic Perspective Camera"); +NAMESPACE_END(mitsuba) From 0e36e3e8f63d2f3cbe981a549de1a3680221bbbe Mon Sep 17 00:00:00 2001 From: SergeyShlyaev <63272131+SergeyShlyaev@users.noreply.github.com> Date: Sun, 6 Sep 2020 17:51:43 -0400 Subject: [PATCH 6/6] Alembic camera, improved doc --- src/sensors/perspective_abc.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sensors/perspective_abc.cpp b/src/sensors/perspective_abc.cpp index 6b551ff5d..2d86f2af8 100644 --- a/src/sensors/perspective_abc.cpp +++ b/src/sensors/perspective_abc.cpp @@ -4,11 +4,6 @@ #include #include -#define show(x) std::cout <<"\033[1m\033[34m"<< "Camera Reading: " << x <<"\033[0m"<< std::endl; -#define shwo(x) std::cout <<"\033[1m\033[34m"<< "Camera Reading: " << x <<"\033[0m"<< std::endl; -#define show2(x, y) std::cout <<"\033[1m\033[34m"<< x << y <<"\033[0m"<< std::endl; - - // Alembic Includes #include #include @@ -41,6 +36,10 @@ Alembic loader for perspective pinhole camera (:monosp:`perspective_abc`) - |transform| - Specifies an optional camera-to-world transformation. (Default: none (i.e. camera space = world space)) + * - convert_to_z_up + - |bool| + - Optionally convert camera so up vector is +Z (Mitsuba default). Useful if camera was exported + - from software that is Y-up. For example, Maya or Houdini. (Default: |false|) This plugin brings support for reading cameras from Alembic file. It reads position and orientation data as well as field of view and near/far clipping planes. @@ -81,7 +80,7 @@ class AlembicPerspectiveCamera final : public ProjectiveCamera fail("file not found"); m_shape_name = props.string("shape_name", ""); - const bool convert_to_z_up = props.bool_("convert_to_z_up", true); + const bool convert_to_z_up = props.bool_("convert_to_z_up", false); m_use_shape_name = false; if (!m_shape_name.empty()) @@ -98,6 +97,7 @@ class AlembicPerspectiveCamera final : public ProjectiveCamera ICamera camera; find_camera_recursively(archive_top, sample_index, alembic_transform, camera); + // fail if camera was not found in file if (!camera.valid()){ std::string error_msg = "did not found camera in file"; if (m_use_shape_name)