Blender V4.3
alembic.h
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
2 *
3 * SPDX-License-Identifier: Apache-2.0 */
4
5#pragma once
6
7#include "graph/node.h"
8#include "scene/attribute.h"
9#include "scene/procedural.h"
10#include "util/set.h"
11#include "util/transform.h"
12#include "util/vector.h"
13
14#ifdef WITH_ALEMBIC
15
16# include <Alembic/AbcCoreFactory/All.h>
17# include <Alembic/AbcGeom/All.h>
18
20
21class AlembicProcedural;
22class Geometry;
23class Object;
24class Progress;
25class Shader;
26
27using MatrixSampleMap = std::map<Alembic::Abc::chrono_t, Alembic::Abc::M44d>;
28
29struct MatrixSamplesData {
30 MatrixSampleMap *samples = nullptr;
31 Alembic::AbcCoreAbstract::TimeSamplingPtr time_sampling;
32};
33
34/* Helpers to detect if some type is a `ccl::array`. */
35template<typename> struct is_array : public std::false_type {};
36
37template<typename T> struct is_array<array<T>> : public std::true_type {};
38
39/* Holds the data for a cache lookup at a given time, as well as information to
40 * help disambiguate successes or failures to get data from the cache. */
41template<typename T> class CacheLookupResult {
42 enum class State {
43 NEW_DATA,
44 ALREADY_LOADED,
45 NO_DATA_FOR_TIME,
46 };
47
48 T *data;
49 State state;
50
51 protected:
52 /* Prevent default construction outside of the class: for a valid result, we
53 * should use the static functions below. */
54 CacheLookupResult() = default;
55
56 public:
57 static CacheLookupResult new_data(T *data_)
58 {
59 CacheLookupResult result;
60 result.data = data_;
61 result.state = State::NEW_DATA;
62 return result;
63 }
64
65 static CacheLookupResult no_data_found_for_time()
66 {
67 CacheLookupResult result;
68 result.data = nullptr;
69 result.state = State::NO_DATA_FOR_TIME;
70 return result;
71 }
72
73 static CacheLookupResult already_loaded()
74 {
75 CacheLookupResult result;
76 result.data = nullptr;
77 result.state = State::ALREADY_LOADED;
78 return result;
79 }
80
81 /* This should only be call if new data is available. */
82 const T &get_data() const
83 {
84 assert(state == State::NEW_DATA);
85 assert(data != nullptr);
86 return *data;
87 }
88
89 T *get_data_or_null() const
90 {
91 // data_ should already be null if there is no new data so no need to check
92 return data;
93 }
94
95 bool has_new_data() const
96 {
97 return state == State::NEW_DATA;
98 }
99
100 bool has_already_loaded() const
101 {
102 return state == State::ALREADY_LOADED;
103 }
104
105 bool has_no_data_for_time() const
106 {
107 return state == State::NO_DATA_FOR_TIME;
108 }
109};
110
111/* Store the data set for an animation at every time points, or at the beginning of the animation
112 * for constant data.
113 *
114 * The data is supposed to be stored in chronological order, and is looked up using the current
115 * animation time in seconds using the TimeSampling from the Alembic property. */
116template<typename T> class DataStore {
117 /* Holds information to map a cache entry for a given time to an index into the data array. */
118 struct TimeIndexPair {
119 /* Frame time for this entry. */
120 double time = 0;
121 /* Frame time for the data pointed to by `index`. */
122 double source_time = 0;
123 /* Index into the data array. */
124 size_t index = 0;
125 };
126
127 /* This is the actual data that is stored. We deduplicate data across frames to avoid storing
128 * values if they have not changed yet (e.g. the triangles for a building before fracturing, or a
129 * fluid simulation before a break or splash) */
130 vector<T> data{};
131
132 /* This is used to map they entry for a given time to an index into the data array, multiple
133 * frames can point to the same index. */
134 vector<TimeIndexPair> index_data_map{};
135
136 Alembic::AbcCoreAbstract::TimeSampling time_sampling{};
137
138 double last_loaded_time = std::numeric_limits<double>::max();
139
140 public:
141 /* Keys used to compare values. */
142 Alembic::AbcCoreAbstract::ArraySample::Key key1;
143 Alembic::AbcCoreAbstract::ArraySample::Key key2;
144
145 void set_time_sampling(Alembic::AbcCoreAbstract::TimeSampling time_sampling_)
146 {
147 time_sampling = time_sampling_;
148 }
149
150 Alembic::AbcCoreAbstract::TimeSampling get_time_sampling() const
151 {
152 return time_sampling;
153 }
154
155 /* Get the data for the specified time.
156 * Return nullptr if there is no data or if the data for this time was already loaded. */
157 CacheLookupResult<T> data_for_time(double time)
158 {
159 if (size() == 0) {
160 return CacheLookupResult<T>::no_data_found_for_time();
161 }
162
163 const TimeIndexPair &index = get_index_for_time(time);
164
165 if (index.index == -1ul) {
166 return CacheLookupResult<T>::no_data_found_for_time();
167 }
168
169 if (last_loaded_time == index.time || last_loaded_time == index.source_time) {
170 return CacheLookupResult<T>::already_loaded();
171 }
172
173 last_loaded_time = index.source_time;
174
175 assert(index.index < data.size());
176
177 return CacheLookupResult<T>::new_data(&data[index.index]);
178 }
179
180 /* get the data for the specified time, but do not check if the data was already loaded for this
181 * time return nullptr if there is no data */
182 CacheLookupResult<T> data_for_time_no_check(double time)
183 {
184 if (size() == 0) {
185 return CacheLookupResult<T>::no_data_found_for_time();
186 }
187
188 const TimeIndexPair &index = get_index_for_time(time);
189
190 if (index.index == -1ul) {
191 return CacheLookupResult<T>::no_data_found_for_time();
192 }
193
194 assert(index.index < data.size());
195
196 return CacheLookupResult<T>::new_data(&data[index.index]);
197 }
198
199 void add_data(T &data_, double time)
200 {
201 index_data_map.push_back({time, time, data.size()});
202
203 if constexpr (is_array<T>::value) {
204 data.emplace_back();
205 data.back().steal_data(data_);
206 return;
207 }
208
209 data.push_back(data_);
210 }
211
212 void reuse_data_for_last_time(double time)
213 {
214 const TimeIndexPair &data_index = index_data_map.back();
215 index_data_map.push_back({time, data_index.source_time, data_index.index});
216 }
217
218 void add_no_data(double time)
219 {
220 index_data_map.push_back({time, time, -1ul});
221 }
222
223 bool is_constant() const
224 {
225 return data.size() <= 1;
226 }
227
228 size_t size() const
229 {
230 return data.size();
231 }
232
233 void clear()
234 {
235 invalidate_last_loaded_time();
236 data.clear();
237 index_data_map.clear();
238 }
239
240 void invalidate_last_loaded_time()
241 {
242 last_loaded_time = std::numeric_limits<double>::max();
243 }
244
245 /* Copy the data for the specified time to the node's socket. If there is no
246 * data for this time or it was already loaded, do nothing. */
247 void copy_to_socket(double time, Node *node, const SocketType *socket)
248 {
249 CacheLookupResult<T> result = data_for_time(time);
250
251 if (!result.has_new_data()) {
252 return;
253 }
254
255 /* TODO(kevindietrich): arrays are emptied when passed to the sockets, so for now we copy the
256 * arrays to avoid reloading the data */
257 T value = result.get_data();
258 node->set(*socket, value);
259 }
260
261 size_t memory_used() const
262 {
263 if constexpr (is_array<T>::value) {
264 size_t mem_used = 0;
265
266 for (const T &array : data) {
267 mem_used += array.size() * sizeof(array[0]);
268 }
269
270 return mem_used;
271 }
272
273 return data.size() * sizeof(T);
274 }
275
276 private:
277 const TimeIndexPair &get_index_for_time(double time) const
278 {
279 std::pair<size_t, Alembic::Abc::chrono_t> index_pair;
280 index_pair = time_sampling.getNearIndex(time, index_data_map.size());
281 return index_data_map[index_pair.first];
282 }
283};
284
285/* Actual cache for the stored data.
286 * This caches the topological, transformation, and attribute data for a Mesh node or a Hair node
287 * inside of DataStores.
288 */
289struct CachedData {
290 DataStore<Transform> transforms{};
291
292 /* mesh data */
293 DataStore<array<float3>> vertices;
294 DataStore<array<int3>> triangles{};
295 /* triangle "loops" are the polygons' vertices indices used for indexing face varying attributes
296 * (like UVs) */
297 DataStore<array<int>> uv_loops{};
298 DataStore<array<int>> shader{};
299
300 /* subd data */
301 DataStore<array<int>> subd_start_corner;
302 DataStore<array<int>> subd_num_corners;
303 DataStore<array<bool>> subd_smooth;
304 DataStore<array<int>> subd_ptex_offset;
305 DataStore<array<int>> subd_face_corners;
306 DataStore<int> num_ngons;
307 DataStore<array<int>> subd_creases_edge;
308 DataStore<array<float>> subd_creases_weight;
309 DataStore<array<int>> subd_vertex_crease_indices;
310 DataStore<array<float>> subd_vertex_crease_weights;
311
312 /* hair data */
313 DataStore<array<float3>> curve_keys;
314 DataStore<array<float>> curve_radius;
315 DataStore<array<int>> curve_first_key;
316 DataStore<array<int>> curve_shader;
317
318 /* point data */
319 DataStore<array<float3>> points;
320 DataStore<array<float>> radiuses;
321 DataStore<array<int>> points_shader;
322
323 struct CachedAttribute {
326 TypeDesc type_desc;
327 ustring name;
328 DataStore<array<char>> data{};
329 };
330
331 vector<CachedAttribute> attributes{};
332
333 void clear();
334
335 CachedAttribute &add_attribute(const ustring &name,
336 const Alembic::Abc::TimeSampling &time_sampling);
337
338 bool is_constant() const;
339
340 void invalidate_last_loaded_time(bool attributes_only = false);
341
342 void set_time_sampling(Alembic::AbcCoreAbstract::TimeSampling time_sampling);
343
344 size_t memory_used() const;
345};
346
347/* Representation of an Alembic object for the AlembicProcedural.
348 *
349 * The AlembicObject holds the path to the Alembic IObject inside of the archive that is desired
350 * for rendering, as well as the list of shaders that it is using.
351 *
352 * The names of the shaders should correspond to the names of the FaceSets inside of the Alembic
353 * archive for per-triangle shader association. If there is no FaceSets, or the names do not
354 * match, the first shader is used for rendering for all triangles.
355 */
356class AlembicObject : public Node {
357 public:
359
360 /* Path to the IObject inside of the archive. */
361 NODE_SOCKET_API(ustring, path)
362
363 /* Shaders used for rendering. */
365
366 /* Treat this subdivision object as a regular polygon mesh, so no subdivision will be performed.
367 */
368 NODE_SOCKET_API(bool, ignore_subdivision)
369
370 /* Maximum number of subdivisions for ISubD objects. */
371 NODE_SOCKET_API(int, subd_max_level)
372
373 /* Finest level of detail (in pixels) for the subdivision. */
374 NODE_SOCKET_API(float, subd_dicing_rate)
375
376 /* Scale the radius of points and curves. */
377 NODE_SOCKET_API(float, radius_scale)
378
379 AlembicObject();
380 ~AlembicObject();
381
382 private:
383 friend class AlembicProcedural;
384
385 void set_object(Object *object);
386 Object *get_object();
387
388 void load_data_in_cache(CachedData &cached_data,
389 AlembicProcedural *proc,
390 Alembic::AbcGeom::IPolyMeshSchema &schema,
391 Progress &progress);
392 void load_data_in_cache(CachedData &cached_data,
393 AlembicProcedural *proc,
394 Alembic::AbcGeom::ISubDSchema &schema,
395 Progress &progress);
396 void load_data_in_cache(CachedData &cached_data,
397 AlembicProcedural *proc,
398 const Alembic::AbcGeom::ICurvesSchema &schema,
399 Progress &progress);
400 void load_data_in_cache(CachedData &cached_data,
401 AlembicProcedural *proc,
402 const Alembic::AbcGeom::IPointsSchema &schema,
403 Progress &progress);
404
405 bool has_data_loaded() const;
406
407 /* Enumeration used to speed up the discrimination of an IObject as IObject::matches() methods
408 * are too expensive and show up in profiles. */
409 enum AbcSchemaType {
410 INVALID,
411 POLY_MESH,
412 SUBD,
413 CURVES,
414 POINTS,
415 };
416
417 bool need_shader_update = true;
418
419 AlembicObject *instance_of = nullptr;
420
421 Alembic::AbcCoreAbstract::TimeSamplingPtr xform_time_sampling;
422 MatrixSampleMap xform_samples;
423 Alembic::AbcGeom::IObject iobject;
424
425 /* Set if the path points to a valid IObject whose type is supported. */
426 AbcSchemaType schema_type;
427
428 CachedData &get_cached_data()
429 {
430 return cached_data_;
431 }
432
433 bool is_constant() const
434 {
435 return cached_data_.is_constant();
436 }
437
438 void clear_cache()
439 {
440 cached_data_.clear();
441 }
442
443 Object *object = nullptr;
444
445 bool data_loaded = false;
446
447 CachedData cached_data_;
448
449 void setup_transform_cache(CachedData &cached_data, float scale);
450
451 AttributeRequestSet get_requested_attributes();
452};
453
454/* Procedural to render objects from a single Alembic archive.
455 *
456 * Every object desired to be rendered should be passed as an AlembicObject through the objects
457 * socket.
458 *
459 * This procedural will load the data set for the entire animation in memory on the first frame,
460 * and directly set the data for the new frames on the created Nodes if needed. This allows for
461 * faster updates between frames as it avoids reseeking the data on disk.
462 */
463class AlembicProcedural : public Procedural {
464 Alembic::AbcGeom::IArchive archive;
465 bool objects_loaded;
466 Scene *scene_;
467
468 public:
470
471 /* The file path to the Alembic archive */
472 NODE_SOCKET_API(ustring, filepath)
473
474 /* Layers for the Alembic archive. Layers are in the order in which they override data, with the
475 * latter elements overriding the former ones. */
477
478 /* The current frame to render. */
479 NODE_SOCKET_API(float, frame)
480
481 /* The first frame to load data for. */
482 NODE_SOCKET_API(float, start_frame)
483
484 /* The last frame to load data for. */
485 NODE_SOCKET_API(float, end_frame)
486
487 /* Subtracted to the current frame. */
488 NODE_SOCKET_API(float, frame_offset)
489
490 /* The frame rate used for rendering in units of frames per second. */
491 NODE_SOCKET_API(float, frame_rate)
492
493 /* List of AlembicObjects to render. */
495
496 /* Set the default radius to use for curves when the Alembic Curves Schemas do not have radius
497 * information. */
498 NODE_SOCKET_API(float, default_radius)
499
500 /* Multiplier to account for differences in default units for measuring objects in various
501 * software. */
502 NODE_SOCKET_API(float, scale)
503
504 /* Cache controls */
505 NODE_SOCKET_API(bool, use_prefetch)
506
507 /* Memory limit for the cache, if the data does not fit within this limit, rendering is aborted.
508 */
509 NODE_SOCKET_API(int, prefetch_cache_size)
510
511 AlembicProcedural();
512 ~AlembicProcedural();
513
514 /* Populates the Cycles scene with Nodes for every contained AlembicObject on the first
515 * invocation, and updates the data on subsequent invocations if the frame changed. */
516 void generate(Scene *scene, Progress &progress);
517
518 /* Tag for an update only if something was modified. */
519 void tag_update(Scene *scene);
520
521 /* This should be called by scene exporters to request the rendering of an object located
522 * in the Alembic archive at the given path.
523 *
524 * Since we lazily load object, the function does not validate the existence of the object
525 * in the archive. If no objects with such path if found in the archive during the next call
526 * to `generate`, it will be ignored.
527 *
528 * Returns a pointer to an existing or a newly created AlembicObject for the given path. */
529 AlembicObject *get_or_create_object(const ustring &path);
530
531 private:
532 /* Add an object to our list of objects, and tag the socket as modified. */
533 void add_object(AlembicObject *object);
534
535 /* Load the data for all the objects whose data has not yet been loaded. */
536 void load_objects(Progress &progress);
537
538 /* Traverse the Alembic hierarchy to lookup the IObjects for the AlembicObjects that were
539 * specified in our objects socket, and accumulate all of the transformations samples along the
540 * way for each IObject. */
541 void walk_hierarchy(Alembic::AbcGeom::IObject parent,
542 const Alembic::AbcGeom::ObjectHeader &ohead,
543 MatrixSamplesData matrix_samples_data,
544 const unordered_map<string, AlembicObject *> &object_map,
545 Progress &progress);
546
547 /* Read the data for an IPolyMesh at the specified frame_time. Creates corresponding Geometry and
548 * Object Nodes in the Cycles scene if none exist yet. */
549 void read_mesh(AlembicObject *abc_object, Alembic::AbcGeom::Abc::chrono_t frame_time);
550
551 /* Read the data for an ICurves at the specified frame_time. Creates corresponding Geometry and
552 * Object Nodes in the Cycles scene if none exist yet. */
553 void read_curves(AlembicObject *abc_object, Alembic::AbcGeom::Abc::chrono_t frame_time);
554
555 /* Read the data for an IPoints at the specified frame_time. Creates corresponding Geometry and
556 * Object Nodes in the Cycles scene if none exist yet. */
557 void read_points(AlembicObject *abc_object, Alembic::AbcGeom::Abc::chrono_t frame_time);
558
559 /* Read the data for an ISubD at the specified frame_time. Creates corresponding Geometry and
560 * Object Nodes in the Cycles scene if none exist yet. */
561 void read_subd(AlembicObject *abc_object, Alembic::AbcGeom::Abc::chrono_t frame_time);
562
563 void build_caches(Progress &progress);
564
565 size_t get_prefetch_cache_size_in_bytes() const
566 {
567 /* prefetch_cache_size is in megabytes, so convert to bytes. */
568 return static_cast<size_t>(prefetch_cache_size) * 1024 * 1024;
569 }
570};
571
573
574#endif
ATTR_WARN_UNUSED_RESULT const void * element
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
virtual void generate(Scene *scene, Progress &progress)=0
size_t size() const
double time
#define CCL_NAMESPACE_END
#define NODE_SOCKET_API_ARRAY(type_, name)
Definition graph/node.h:63
#define NODE_SOCKET_API(type_, name)
Definition graph/node.h:55
AttributeStandard
AttributeElement
static ulong state[N]
#define T
static void clear(Message &msg)
Definition msgfmt.cc:218
#define NODE_DECLARE
Definition node_type.h:142
long long TypeDesc