/* SPDX-License-Identifier: Apache-2.0 * Copyright 2021-2022 Blender Foundation */ #include "scene/alembic_read.h" #include "scene/alembic.h" #include "scene/mesh.h" #include "util/color.h" #include "util/progress.h" #ifdef WITH_ALEMBIC using namespace Alembic::AbcGeom; CCL_NAMESPACE_BEGIN static float3 make_float3_from_yup(const V3f &v) { return make_float3(v.x, -v.z, v.y); } /* get the sample times to load data for the given the start and end frame of the procedural */ static set get_relevant_sample_times(AlembicProcedural *proc, const TimeSampling &time_sampling, size_t num_samples) { set result; if (num_samples < 2) { result.insert(0.0); return result; } double start_frame; double end_frame; if (proc->get_use_prefetch()) { // load the data for the entire animation start_frame = static_cast(proc->get_start_frame()); end_frame = static_cast(proc->get_end_frame()); } else { // load the data for the current frame start_frame = static_cast(proc->get_frame()); end_frame = start_frame; } const double frame_rate = static_cast(proc->get_frame_rate()); const double start_time = start_frame / frame_rate; const double end_time = (end_frame + 1) / frame_rate; const size_t start_index = time_sampling.getFloorIndex(start_time, num_samples).first; const size_t end_index = time_sampling.getCeilIndex(end_time, num_samples).first; for (size_t i = start_index; i < end_index; ++i) { result.insert(time_sampling.getSampleTime(i)); } return result; } /* Main function to read data, this will iterate over all the relevant sample times for the * duration of the requested animation, and call the DataReadingFunc for each of those sample time. */ template static void read_data_loop(AlembicProcedural *proc, CachedData &cached_data, const Params ¶ms, DataReadingFunc &&func, Progress &progress) { const std::set times = get_relevant_sample_times( proc, *params.time_sampling, params.num_samples); cached_data.set_time_sampling(*params.time_sampling); for (chrono_t time : times) { if (progress.get_cancel()) { return; } func(cached_data, params, time); } } /* Polygon Mesh Geometries. */ /* Compute the vertex normals in case none are present in the IPolyMeshSchema, this is mostly used * to avoid computing them in the GeometryManager in order to speed up data updates. */ static void compute_vertex_normals(CachedData &cache, double current_time) { if (cache.vertices.size() == 0) { return; } CachedData::CachedAttribute &attr_normal = cache.add_attribute( ustring("N"), cache.vertices.get_time_sampling()); attr_normal.std = ATTR_STD_VERTEX_NORMAL; attr_normal.element = ATTR_ELEMENT_VERTEX; attr_normal.type_desc = TypeNormal; const array *vertices = cache.vertices.data_for_time_no_check(current_time).get_data_or_null(); const array *triangles = cache.triangles.data_for_time_no_check(current_time).get_data_or_null(); if (!vertices || !triangles) { attr_normal.data.add_no_data(current_time); return; } array attr_data(vertices->size() * sizeof(float3)); float3 *attr_ptr = reinterpret_cast(attr_data.data()); memset(attr_ptr, 0, vertices->size() * sizeof(float3)); for (size_t t = 0; t < triangles->size(); ++t) { const int3 tri_int3 = triangles->data()[t]; Mesh::Triangle tri{}; tri.v[0] = tri_int3[0]; tri.v[1] = tri_int3[1]; tri.v[2] = tri_int3[2]; const float3 tri_N = tri.compute_normal(vertices->data()); for (int v = 0; v < 3; ++v) { attr_ptr[tri_int3[v]] += tri_N; } } for (size_t v = 0; v < vertices->size(); ++v) { attr_ptr[v] = normalize(attr_ptr[v]); } attr_normal.data.add_data(attr_data, current_time); } static void add_normals(const Int32ArraySamplePtr face_indices, const IN3fGeomParam &normals, double time, CachedData &cached_data) { switch (normals.getScope()) { case kFacevaryingScope: { const ISampleSelector iss = ISampleSelector(time); const IN3fGeomParam::Sample sample = normals.getExpandedValue(iss); if (!sample.valid()) { return; } CachedData::CachedAttribute &attr = cached_data.add_attribute(ustring(normals.getName()), *normals.getTimeSampling()); attr.std = ATTR_STD_VERTEX_NORMAL; const array *vertices = cached_data.vertices.data_for_time_no_check(time).get_data_or_null(); if (!vertices) { return; } array data; data.resize(vertices->size() * sizeof(float3)); float3 *data_float3 = reinterpret_cast(data.data()); const int *face_indices_array = face_indices->get(); const N3fArraySamplePtr values = sample.getVals(); for (size_t i = 0; i < face_indices->size(); ++i) { int point_index = face_indices_array[i]; data_float3[point_index] = make_float3_from_yup(values->get()[i]); } attr.data.add_data(data, time); break; } case kVaryingScope: case kVertexScope: { const ISampleSelector iss = ISampleSelector(time); const IN3fGeomParam::Sample sample = normals.getExpandedValue(iss); if (!sample.valid()) { return; } CachedData::CachedAttribute &attr = cached_data.add_attribute(ustring(normals.getName()), *normals.getTimeSampling()); attr.std = ATTR_STD_VERTEX_NORMAL; const array *vertices = cached_data.vertices.data_for_time_no_check(time).get_data_or_null(); if (!vertices) { return; } array data; data.resize(vertices->size() * sizeof(float3)); float3 *data_float3 = reinterpret_cast(data.data()); const Imath::V3f *values = sample.getVals()->get(); for (size_t i = 0; i < vertices->size(); ++i) { data_float3[i] = make_float3_from_yup(values[i]); } attr.data.add_data(data, time); break; } default: { break; } } } static void add_positions(const P3fArraySamplePtr positions, double time, CachedData &cached_data) { if (!positions) { return; } array vertices; vertices.reserve(positions->size()); for (size_t i = 0; i < positions->size(); i++) { V3f f = positions->get()[i]; vertices.push_back_reserved(make_float3_from_yup(f)); } cached_data.vertices.add_data(vertices, time); } static void add_triangles(const Int32ArraySamplePtr face_counts, const Int32ArraySamplePtr face_indices, double time, CachedData &cached_data, const array &polygon_to_shader) { if (!face_counts || !face_indices) { return; } const size_t num_faces = face_counts->size(); const int *face_counts_array = face_counts->get(); const int *face_indices_array = face_indices->get(); size_t num_triangles = 0; for (size_t i = 0; i < face_counts->size(); i++) { num_triangles += face_counts_array[i] - 2; } array shader; array triangles; array uv_loops; shader.reserve(num_triangles); triangles.reserve(num_triangles); uv_loops.reserve(num_triangles * 3); int index_offset = 0; for (size_t i = 0; i < num_faces; i++) { int current_shader = 0; if (!polygon_to_shader.empty()) { current_shader = polygon_to_shader[i]; } for (int j = 0; j < face_counts_array[i] - 2; j++) { int v0 = face_indices_array[index_offset]; int v1 = face_indices_array[index_offset + j + 1]; int v2 = face_indices_array[index_offset + j + 2]; shader.push_back_reserved(current_shader); /* Alembic orders the loops following the RenderMan convention, so need to go in reverse. */ triangles.push_back_reserved(make_int3(v2, v1, v0)); uv_loops.push_back_reserved(index_offset + j + 2); uv_loops.push_back_reserved(index_offset + j + 1); uv_loops.push_back_reserved(index_offset); } index_offset += face_counts_array[i]; } cached_data.triangles.add_data(triangles, time); cached_data.uv_loops.add_data(uv_loops, time); cached_data.shader.add_data(shader, time); } static array compute_polygon_to_shader_map( const Int32ArraySamplePtr &face_counts, const vector &face_set_shader_index, ISampleSelector sample_sel) { if (face_set_shader_index.empty()) { return {}; } if (!face_counts) { return {}; } if (face_counts->size() == 0) { return {}; } array polygon_to_shader(face_counts->size()); for (const FaceSetShaderIndexPair &pair : face_set_shader_index) { const IFaceSet &face_set = pair.face_set; const IFaceSetSchema face_schem = face_set.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 >= polygon_to_shader.size()) { continue; } polygon_to_shader[pos] = pair.shader_index; } } return polygon_to_shader; } static void read_poly_mesh_geometry(CachedData &cached_data, const PolyMeshSchemaData &data, chrono_t time) { const ISampleSelector iss = ISampleSelector(time); add_positions(data.positions.getValue(iss), time, cached_data); const Int32ArraySamplePtr face_counts = data.face_counts.getValue(iss); const Int32ArraySamplePtr face_indices = data.face_indices.getValue(iss); /* Only copy triangles for other frames if the topology is changing over time as well. */ if (data.topology_variance != kHomogeneousTopology || cached_data.triangles.size() == 0) { bool do_triangles = true; /* Compare key with last one to check whether the topology changed. */ if (cached_data.triangles.size() > 0) { const ArraySample::Key key = face_indices->getKey(); if (key == cached_data.triangles.key1) { do_triangles = false; } cached_data.triangles.key1 = key; } if (do_triangles) { const array polygon_to_shader = compute_polygon_to_shader_map( face_counts, data.shader_face_sets, iss); add_triangles(face_counts, face_indices, time, cached_data, polygon_to_shader); } else { cached_data.triangles.reuse_data_for_last_time(time); cached_data.uv_loops.reuse_data_for_last_time(time); cached_data.shader.reuse_data_for_last_time(time); } /* Initialize the first key. */ if (data.topology_variance != kHomogeneousTopology && cached_data.triangles.size() == 1) { cached_data.triangles.key1 = face_indices->getKey(); } } if (data.normals.valid()) { add_normals(face_indices, data.normals, time, cached_data); } else { compute_vertex_normals(cached_data, time); } } void read_geometry_data(AlembicProcedural *proc, CachedData &cached_data, const PolyMeshSchemaData &data, Progress &progress) { read_data_loop(proc, cached_data, data, read_poly_mesh_geometry, progress); } /* Subdivision Geometries */ static void add_subd_polygons(CachedData &cached_data, const SubDSchemaData &data, chrono_t time) { const ISampleSelector iss = ISampleSelector(time); const Int32ArraySamplePtr face_counts = data.face_counts.getValue(iss); const Int32ArraySamplePtr face_indices = data.face_indices.getValue(iss); array subd_start_corner; array shader; array subd_num_corners; array subd_smooth; array subd_ptex_offset; array subd_face_corners; array uv_loops; const size_t num_faces = face_counts->size(); const int *face_counts_array = face_counts->get(); const int *face_indices_array = face_indices->get(); int num_ngons = 0; int num_corners = 0; for (size_t i = 0; i < face_counts->size(); i++) { num_ngons += (face_counts_array[i] == 4 ? 0 : 1); num_corners += face_counts_array[i]; } subd_start_corner.reserve(num_faces); subd_num_corners.reserve(num_faces); subd_smooth.reserve(num_faces); subd_ptex_offset.reserve(num_faces); shader.reserve(num_faces); subd_face_corners.reserve(num_corners); uv_loops.reserve(num_corners); int start_corner = 0; int current_shader = 0; int ptex_offset = 0; const array polygon_to_shader = compute_polygon_to_shader_map( face_counts, data.shader_face_sets, iss); for (size_t i = 0; i < face_counts->size(); i++) { num_corners = face_counts_array[i]; if (!polygon_to_shader.empty()) { current_shader = polygon_to_shader[i]; } subd_start_corner.push_back_reserved(start_corner); subd_num_corners.push_back_reserved(num_corners); for (int j = 0; j < num_corners; ++j) { subd_face_corners.push_back_reserved(face_indices_array[start_corner + j]); uv_loops.push_back_reserved(start_corner + j); } shader.push_back_reserved(current_shader); subd_smooth.push_back_reserved(1); subd_ptex_offset.push_back_reserved(ptex_offset); ptex_offset += (num_corners == 4 ? 1 : num_corners); start_corner += num_corners; } cached_data.shader.add_data(shader, time); cached_data.subd_start_corner.add_data(subd_start_corner, time); cached_data.subd_num_corners.add_data(subd_num_corners, time); cached_data.subd_smooth.add_data(subd_smooth, time); cached_data.subd_ptex_offset.add_data(subd_ptex_offset, time); cached_data.subd_face_corners.add_data(subd_face_corners, time); cached_data.num_ngons.add_data(num_ngons, time); cached_data.uv_loops.add_data(uv_loops, time); } static void add_subd_edge_creases(CachedData &cached_data, const SubDSchemaData &data, chrono_t time) { if (!(data.crease_indices.valid() && data.crease_lengths.valid() && data.crease_sharpnesses.valid())) { return; } const ISampleSelector iss = ISampleSelector(time); const Int32ArraySamplePtr creases_length = data.crease_lengths.getValue(iss); const Int32ArraySamplePtr creases_indices = data.crease_indices.getValue(iss); const FloatArraySamplePtr creases_sharpnesses = data.crease_sharpnesses.getValue(iss); if (creases_length && creases_indices && creases_sharpnesses) { array creases_edge; array creases_weight; creases_edge.reserve(creases_sharpnesses->size() * 2); creases_weight.reserve(creases_sharpnesses->size()); int length_offset = 0; int weight_offset = 0; for (size_t c = 0; c < creases_length->size(); ++c) { const int crease_length = creases_length->get()[c]; for (size_t j = 0; j < crease_length - 1; ++j) { creases_edge.push_back_reserved(creases_indices->get()[length_offset + j]); creases_edge.push_back_reserved(creases_indices->get()[length_offset + j + 1]); creases_weight.push_back_reserved(creases_sharpnesses->get()[weight_offset++]); } length_offset += crease_length; } cached_data.subd_creases_edge.add_data(creases_edge, time); cached_data.subd_creases_weight.add_data(creases_weight, time); } } static void add_subd_vertex_creases(CachedData &cached_data, const SubDSchemaData &data, chrono_t time) { if (!(data.corner_indices.valid() && data.crease_sharpnesses.valid())) { return; } const ISampleSelector iss = ISampleSelector(time); const Int32ArraySamplePtr creases_indices = data.crease_indices.getValue(iss); const FloatArraySamplePtr creases_sharpnesses = data.crease_sharpnesses.getValue(iss); if (!(creases_indices && creases_sharpnesses) || creases_indices->size() != creases_sharpnesses->size()) { return; } array sharpnesses; sharpnesses.reserve(creases_indices->size()); array indices; indices.reserve(creases_indices->size()); for (size_t i = 0; i < creases_indices->size(); i++) { indices.push_back_reserved((*creases_indices)[i]); sharpnesses.push_back_reserved((*creases_sharpnesses)[i]); } cached_data.subd_vertex_crease_indices.add_data(indices, time); cached_data.subd_vertex_crease_weights.add_data(sharpnesses, time); } static void read_subd_geometry(CachedData &cached_data, const SubDSchemaData &data, chrono_t time) { const ISampleSelector iss = ISampleSelector(time); add_positions(data.positions.getValue(iss), time, cached_data); if (data.topology_variance != kHomogeneousTopology || cached_data.shader.size() == 0) { add_subd_polygons(cached_data, data, time); add_subd_edge_creases(cached_data, data, time); add_subd_vertex_creases(cached_data, data, time); } } void read_geometry_data(AlembicProcedural *proc, CachedData &cached_data, const SubDSchemaData &data, Progress &progress) { read_data_loop(proc, cached_data, data, read_subd_geometry, progress); } /* Curve Geometries. */ static void read_curves_data(CachedData &cached_data, const CurvesSchemaData &data, chrono_t time) { const ISampleSelector iss = ISampleSelector(time); const Int32ArraySamplePtr curves_num_vertices = data.num_vertices.getValue(iss); const P3fArraySamplePtr position = data.positions.getValue(iss); FloatArraySamplePtr radiuses; if (data.widths.valid()) { IFloatGeomParam::Sample wsample = data.widths.getExpandedValue(iss); radiuses = wsample.getVals(); } const bool do_radius = (radiuses != nullptr) && (radiuses->size() > 1); float radius = (radiuses && radiuses->size() == 1) ? (*radiuses)[0] : data.default_radius; array curve_keys; array curve_radius; array curve_first_key; array curve_shader; const bool is_homogeneous = data.topology_variance == kHomogeneousTopology; curve_keys.reserve(position->size()); curve_radius.reserve(position->size()); curve_first_key.reserve(curves_num_vertices->size()); curve_shader.reserve(curves_num_vertices->size()); int offset = 0; for (size_t i = 0; i < curves_num_vertices->size(); i++) { const int num_vertices = curves_num_vertices->get()[i]; for (int j = 0; j < num_vertices; j++) { const V3f &f = position->get()[offset + j]; // todo(@kevindietrich): we are reading too much data? curve_keys.push_back_slow(make_float3_from_yup(f)); if (do_radius) { radius = (*radiuses)[offset + j]; } curve_radius.push_back_slow(radius * data.radius_scale); } if (!is_homogeneous || cached_data.curve_first_key.size() == 0) { curve_first_key.push_back_reserved(offset); curve_shader.push_back_reserved(0); } offset += num_vertices; } cached_data.curve_keys.add_data(curve_keys, time); cached_data.curve_radius.add_data(curve_radius, time); if (!is_homogeneous || cached_data.curve_first_key.size() == 0) { cached_data.curve_first_key.add_data(curve_first_key, time); cached_data.curve_shader.add_data(curve_shader, time); } } void read_geometry_data(AlembicProcedural *proc, CachedData &cached_data, const CurvesSchemaData &data, Progress &progress) { read_data_loop(proc, cached_data, data, read_curves_data, progress); } /* Points Geometries. */ static void read_points_data(CachedData &cached_data, const PointsSchemaData &data, chrono_t time) { const ISampleSelector iss = ISampleSelector(time); const P3fArraySamplePtr position = data.positions.getValue(iss); FloatArraySamplePtr radiuses; array a_positions; array a_radius; array a_shader; a_positions.reserve(position->size()); a_radius.reserve(position->size()); a_shader.reserve(position->size()); if (data.radiuses.valid()) { IFloatGeomParam::Sample wsample = data.radiuses.getExpandedValue(iss); radiuses = wsample.getVals(); } const bool do_radius = (radiuses != nullptr) && (radiuses->size() > 1); float radius = (radiuses && radiuses->size() == 1) ? (*radiuses)[0] : data.default_radius; int offset = 0; for (size_t i = 0; i < position->size(); i++) { const V3f &f = position->get()[offset + i]; a_positions.push_back_slow(make_float3_from_yup(f)); if (do_radius) { radius = (*radiuses)[offset + i]; a_radius.push_back_slow(radius); } a_shader.push_back_slow((int)0); } cached_data.points.add_data(a_positions, time); cached_data.radiuses.add_data(a_radius, time); cached_data.points_shader.add_data(a_shader, time); } void read_geometry_data(AlembicProcedural *proc, CachedData &cached_data, const PointsSchemaData &data, Progress &progress) { read_data_loop(proc, cached_data, data, read_points_data, progress); } /* Attributes conversions. */ /* Type traits for converting between Alembic and Cycles types. */ template struct value_type_converter { using cycles_type = float; /* Use `TypeDesc::FLOAT` instead of `TypeFloat` to work around a compiler bug in gcc 11. */ static constexpr TypeDesc type_desc = TypeDesc::FLOAT; static constexpr const char *type_name = "float (default)"; static cycles_type convert_value(T value) { return static_cast(value); } }; template<> struct value_type_converter { using cycles_type = float2; static constexpr TypeDesc type_desc = TypeFloat2; static constexpr const char *type_name = "float2"; static cycles_type convert_value(Imath::V2f value) { return make_float2(value.x, value.y); } }; template<> struct value_type_converter { using cycles_type = float3; static constexpr TypeDesc type_desc = TypeVector; static constexpr const char *type_name = "float3"; static cycles_type convert_value(Imath::V3f value) { return make_float3_from_yup(value); } }; template<> struct value_type_converter { using cycles_type = uchar4; static constexpr TypeDesc type_desc = TypeRGBA; static constexpr const char *type_name = "rgb"; static cycles_type convert_value(Imath::C3f value) { return color_float_to_byte(make_float3(value.x, value.y, value.z)); } }; template<> struct value_type_converter { using cycles_type = uchar4; static constexpr TypeDesc type_desc = TypeRGBA; static constexpr const char *type_name = "rgba"; static cycles_type convert_value(Imath::C4f value) { return color_float4_to_uchar4(make_float4(value.r, value.g, value.b, value.a)); } }; /* Main function used to read attributes of any type. */ template static void process_attribute(CachedData &cache, CachedData::CachedAttribute &attribute, GeometryScope scope, const typename ITypedGeomParam::Sample &sample, double time) { using abc_type = typename TRAIT::value_type; using cycles_type = typename value_type_converter::cycles_type; const TypedArraySample &values = *sample.getVals(); switch (scope) { case kConstantScope: case kVertexScope: { const array *vertices = cache.vertices.data_for_time_no_check(time).get_data_or_null(); if (!vertices) { attribute.data.add_no_data(time); return; } if (vertices->size() != values.size()) { attribute.data.add_no_data(time); return; } array data(vertices->size() * sizeof(cycles_type)); cycles_type *pod_typed_data = reinterpret_cast(data.data()); for (size_t i = 0; i < values.size(); ++i) { *pod_typed_data++ = value_type_converter::convert_value(values[i]); } attribute.data.add_data(data, time); break; } case kVaryingScope: { const array *triangles = cache.triangles.data_for_time_no_check(time).get_data_or_null(); if (!triangles) { attribute.data.add_no_data(time); return; } array data(triangles->size() * 3 * sizeof(cycles_type)); cycles_type *pod_typed_data = reinterpret_cast(data.data()); for (const int3 &tri : *triangles) { *pod_typed_data++ = value_type_converter::convert_value(values[tri.x]); *pod_typed_data++ = value_type_converter::convert_value(values[tri.y]); *pod_typed_data++ = value_type_converter::convert_value(values[tri.z]); } attribute.data.add_data(data, time); break; } default: { break; } } } /* UVs are processed separately as their indexing is based on loops, instead of vertices or * corners. */ static void process_uvs(CachedData &cache, CachedData::CachedAttribute &attribute, GeometryScope scope, const IV2fGeomParam::Sample &sample, double time) { if (scope != kFacevaryingScope && scope != kVaryingScope && scope != kVertexScope) { return; } const array *uv_loops = cache.uv_loops.data_for_time_no_check(time).get_data_or_null(); /* It's ok to not have loop indices, as long as the scope is not face-varying. */ if (!uv_loops && scope == kFacevaryingScope) { return; } const array *triangles = cache.triangles.data_for_time_no_check(time).get_data_or_null(); const array *corners = cache.subd_face_corners.data_for_time_no_check(time).get_data_or_null(); array data; if (triangles) { data.resize(triangles->size() * 3 * sizeof(float2)); } else if (corners) { data.resize(corners->size() * sizeof(float2)); } else { return; } float2 *data_float2 = reinterpret_cast(data.data()); const uint32_t *indices = sample.getIndices()->get(); const V2f *values = sample.getVals()->get(); if (scope == kFacevaryingScope) { for (const int uv_loop_index : *uv_loops) { const uint32_t index = indices[uv_loop_index]; *data_float2++ = make_float2(values[index][0], values[index][1]); } } else if (scope == kVaryingScope || scope == kVertexScope) { if (triangles) { for (size_t i = 0; i < triangles->size(); i++) { const int3 t = (*triangles)[i]; *data_float2++ = make_float2(values[t.x][0], values[t.x][1]); *data_float2++ = make_float2(values[t.y][0], values[t.y][1]); *data_float2++ = make_float2(values[t.z][0], values[t.z][1]); } } else if (corners) { for (size_t i = 0; i < corners->size(); i++) { const int c = (*corners)[i]; *data_float2++ = make_float2(values[c][0], values[c][1]); } } } attribute.data.add_data(data, time); } /* Type of the function used to parse one time worth of data, either process_uvs or * process_attribute_generic. */ template using process_callback_type = void (*)(CachedData &, CachedData::CachedAttribute &, GeometryScope, const typename ITypedGeomParam::Sample &, double); /* Main loop to process the attributes, this will look at the given param's TimeSampling and * extract data based on which frame time is requested by the procedural and execute the callback * for each of those requested time. */ template static void read_attribute_loop(AlembicProcedural *proc, CachedData &cache, const ITypedGeomParam ¶m, process_callback_type callback, Progress &progress, AttributeStandard std = ATTR_STD_NONE) { const std::set times = get_relevant_sample_times( proc, *param.getTimeSampling(), param.getNumSamples()); if (times.empty()) { return; } std::string name = param.getName(); if (std == ATTR_STD_UV) { std::string uv_source_name = Alembic::Abc::GetSourceName(param.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 (!uv_source_name.empty()) { name = uv_source_name; } } CachedData::CachedAttribute &attribute = cache.add_attribute(ustring(name), *param.getTimeSampling()); using abc_type = typename TRAIT::value_type; attribute.data.set_time_sampling(*param.getTimeSampling()); attribute.std = std; attribute.type_desc = value_type_converter::type_desc; if (attribute.type_desc == TypeRGBA) { attribute.element = ATTR_ELEMENT_CORNER_BYTE; } else { if (param.getScope() == kVaryingScope || param.getScope() == kFacevaryingScope) { attribute.element = ATTR_ELEMENT_CORNER; } else { attribute.element = ATTR_ELEMENT_VERTEX; } } for (const chrono_t time : times) { if (progress.get_cancel()) { return; } ISampleSelector iss = ISampleSelector(time); typename ITypedGeomParam::Sample sample; param.getIndexed(sample, iss); if (!sample.valid()) { continue; } if (!sample.getVals()) { attribute.data.add_no_data(time); continue; } /* Check whether we already loaded constant data. */ if (attribute.data.size() != 0) { if (param.isConstant()) { return; } const ArraySample::Key indices_key = sample.getIndices()->getKey(); const ArraySample::Key values_key = sample.getVals()->getKey(); const bool is_same_as_last_time = (indices_key == attribute.data.key1 && values_key == attribute.data.key2); attribute.data.key1 = indices_key; attribute.data.key2 = values_key; if (is_same_as_last_time) { attribute.data.reuse_data_for_last_time(time); continue; } } callback(cache, attribute, param.getScope(), sample, time); } } /* Attributes requests. */ /* This structure is used to tell which ICoumpoundProperty the PropertyHeader comes from, as we * need the parent when downcasting to the proper type. */ struct PropHeaderAndParent { const PropertyHeader *prop; ICompoundProperty parent; }; /* Parse the ICompoundProperty to look for properties whose names appear in the * AttributeRequestSet. This also looks into any child ICompoundProperty of the given * ICompoundProperty. If no property of the given name is found, let it be that way, Cycles will * use a zero value for the missing attribute. */ static void parse_requested_attributes_recursive(const AttributeRequestSet &requested_attributes, const ICompoundProperty &arb_geom_params, vector &requested_properties) { if (!arb_geom_params.valid()) { return; } for (const AttributeRequest &req : requested_attributes.requests) { const PropertyHeader *property_header = arb_geom_params.getPropertyHeader(req.name.c_str()); if (!property_header) { continue; } requested_properties.push_back({property_header, arb_geom_params}); } /* Look into children compound properties. */ for (size_t i = 0; i < arb_geom_params.getNumProperties(); ++i) { const PropertyHeader &property_header = arb_geom_params.getPropertyHeader(i); if (property_header.isCompound()) { ICompoundProperty compound_property = ICompoundProperty(arb_geom_params, property_header.getName()); parse_requested_attributes_recursive( requested_attributes, compound_property, requested_properties); } } } /* Main entry point for parsing requested attributes from an ICompoundProperty, this exists so that * we can simply return the list of properties instead of allocating it on the stack and passing it * as a parameter. */ static vector parse_requested_attributes( const AttributeRequestSet &requested_attributes, const ICompoundProperty &arb_geom_params) { vector requested_properties; parse_requested_attributes_recursive( requested_attributes, arb_geom_params, requested_properties); return requested_properties; } /* Read the attributes requested by the shaders from the archive. This will recursively find named * attributes from the AttributeRequestSet in the ICompoundProperty and any of its compound child. * The attributes are added to the CachedData's attribute list. For each attribute we will try to * deduplicate data across consecutive frames. */ void read_attributes(AlembicProcedural *proc, CachedData &cache, const ICompoundProperty &arb_geom_params, const IV2fGeomParam &default_uvs_param, const AttributeRequestSet &requested_attributes, Progress &progress) { if (default_uvs_param.valid()) { /* Only the default UVs should be treated as the standard UV attribute. */ read_attribute_loop(proc, cache, default_uvs_param, process_uvs, progress, ATTR_STD_UV); } vector requested_properties = parse_requested_attributes( requested_attributes, arb_geom_params); for (const PropHeaderAndParent &prop_and_parent : requested_properties) { if (progress.get_cancel()) { return; } const PropertyHeader *prop = prop_and_parent.prop; const ICompoundProperty &parent = prop_and_parent.parent; if (IBoolGeomParam::matches(*prop)) { const IBoolGeomParam ¶m = IBoolGeomParam(parent, prop->getName()); read_attribute_loop(proc, cache, param, process_attribute, progress); } else if (IInt32GeomParam::matches(*prop)) { const IInt32GeomParam ¶m = IInt32GeomParam(parent, prop->getName()); read_attribute_loop(proc, cache, param, process_attribute, progress); } else if (IFloatGeomParam::matches(*prop)) { const IFloatGeomParam ¶m = IFloatGeomParam(parent, prop->getName()); read_attribute_loop(proc, cache, param, process_attribute, progress); } else if (IV2fGeomParam::matches(*prop)) { const IV2fGeomParam ¶m = IV2fGeomParam(parent, prop->getName()); if (Alembic::AbcGeom::isUV(*prop)) { read_attribute_loop(proc, cache, param, process_uvs, progress); } else { read_attribute_loop(proc, cache, param, process_attribute, progress); } } else if (IV3fGeomParam::matches(*prop)) { const IV3fGeomParam ¶m = IV3fGeomParam(parent, prop->getName()); read_attribute_loop(proc, cache, param, process_attribute, progress); } else if (IN3fGeomParam::matches(*prop)) { const IN3fGeomParam ¶m = IN3fGeomParam(parent, prop->getName()); read_attribute_loop(proc, cache, param, process_attribute, progress); } else if (IC3fGeomParam::matches(*prop)) { const IC3fGeomParam ¶m = IC3fGeomParam(parent, prop->getName()); read_attribute_loop(proc, cache, param, process_attribute, progress); } else if (IC4fGeomParam::matches(*prop)) { const IC4fGeomParam ¶m = IC4fGeomParam(parent, prop->getName()); read_attribute_loop(proc, cache, param, process_attribute, progress); } } cache.invalidate_last_loaded_time(true); } CCL_NAMESPACE_END #endif