Cycles: use viewport duplicator visibility on preview
[blender.git] / intern / cycles / blender / blender_object.cpp
1 /*
2  * Copyright 2011-2013 Blender Foundation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "render/camera.h"
18 #include "render/integrator.h"
19 #include "render/graph.h"
20 #include "render/light.h"
21 #include "render/mesh.h"
22 #include "render/object.h"
23 #include "render/scene.h"
24 #include "render/nodes.h"
25 #include "render/particles.h"
26 #include "render/shader.h"
27
28 #include "blender/blender_object_cull.h"
29 #include "blender/blender_sync.h"
30 #include "blender/blender_util.h"
31
32 #include "util/util_foreach.h"
33 #include "util/util_hash.h"
34 #include "util/util_logging.h"
35
36 CCL_NAMESPACE_BEGIN
37
38 /* Utilities */
39
40 bool BlenderSync::BKE_object_is_modified(BL::Object& b_ob)
41 {
42         /* test if we can instance or if the object is modified */
43         if(b_ob.type() == BL::Object::type_META) {
44                 /* multi-user and dupli metaballs are fused, can't instance */
45                 return true;
46         }
47         else if(ccl::BKE_object_is_modified(b_ob, b_scene, preview)) {
48                 /* modifiers */
49                 return true;
50         }
51         else {
52                 /* object level material links */
53                 BL::Object::material_slots_iterator slot;
54                 for(b_ob.material_slots.begin(slot); slot != b_ob.material_slots.end(); ++slot)
55                         if(slot->link() == BL::MaterialSlot::link_OBJECT)
56                                 return true;
57         }
58
59         return false;
60 }
61
62 bool BlenderSync::object_is_mesh(BL::Object& b_ob)
63 {
64         BL::ID b_ob_data = b_ob.data();
65
66         if(!b_ob_data) {
67                 return false;
68         }
69
70         if(b_ob.type() == BL::Object::type_CURVE) {
71                 /* Skip exporting curves without faces, overhead can be
72                  * significant if there are many for path animation. */
73                 BL::Curve b_curve(b_ob.data());
74
75                 return (b_curve.bevel_object() ||
76                         b_curve.extrude() != 0.0f ||
77                         b_curve.bevel_depth() != 0.0f ||
78                         b_curve.dimensions() == BL::Curve::dimensions_2D ||
79                         b_ob.modifiers.length());
80         }
81         else {
82                 return (b_ob_data.is_a(&RNA_Mesh) ||
83                         b_ob_data.is_a(&RNA_Curve) ||
84                         b_ob_data.is_a(&RNA_MetaBall));
85         }
86 }
87
88 bool BlenderSync::object_is_light(BL::Object& b_ob)
89 {
90         BL::ID b_ob_data = b_ob.data();
91
92         return (b_ob_data && b_ob_data.is_a(&RNA_Lamp));
93 }
94
95 static uint object_ray_visibility(BL::Object& b_ob)
96 {
97         PointerRNA cvisibility = RNA_pointer_get(&b_ob.ptr, "cycles_visibility");
98         uint flag = 0;
99
100         flag |= get_boolean(cvisibility, "camera")? PATH_RAY_CAMERA: 0;
101         flag |= get_boolean(cvisibility, "diffuse")? PATH_RAY_DIFFUSE: 0;
102         flag |= get_boolean(cvisibility, "glossy")? PATH_RAY_GLOSSY: 0;
103         flag |= get_boolean(cvisibility, "transmission")? PATH_RAY_TRANSMIT: 0;
104         flag |= get_boolean(cvisibility, "shadow")? PATH_RAY_SHADOW: 0;
105         flag |= get_boolean(cvisibility, "scatter")? PATH_RAY_VOLUME_SCATTER: 0;
106
107         return flag;
108 }
109
110 /* Light */
111
112 void BlenderSync::sync_light(BL::Object& b_parent,
113                              int persistent_id[OBJECT_PERSISTENT_ID_SIZE],
114                              BL::Object& b_ob,
115                              BL::Object& b_ob_instance,
116                              int random_id,
117                              Transform& tfm,
118                              bool *use_portal)
119 {
120         /* test if we need to sync */
121         Light *light;
122         ObjectKey key(b_parent, persistent_id, b_ob_instance);
123
124         if(!light_map.sync(&light, b_ob, b_parent, key)) {
125                 if(light->is_portal)
126                         *use_portal = true;
127                 return;
128         }
129         
130         BL::Lamp b_lamp(b_ob.data());
131
132         /* type */
133         switch(b_lamp.type()) {
134                 case BL::Lamp::type_POINT: {
135                         BL::PointLamp b_point_lamp(b_lamp);
136                         light->size = b_point_lamp.shadow_soft_size();
137                         light->type = LIGHT_POINT;
138                         break;
139                 }
140                 case BL::Lamp::type_SPOT: {
141                         BL::SpotLamp b_spot_lamp(b_lamp);
142                         light->size = b_spot_lamp.shadow_soft_size();
143                         light->type = LIGHT_SPOT;
144                         light->spot_angle = b_spot_lamp.spot_size();
145                         light->spot_smooth = b_spot_lamp.spot_blend();
146                         break;
147                 }
148                 case BL::Lamp::type_HEMI: {
149                         light->type = LIGHT_DISTANT;
150                         light->size = 0.0f;
151                         break;
152                 }
153                 case BL::Lamp::type_SUN: {
154                         BL::SunLamp b_sun_lamp(b_lamp);
155                         light->size = b_sun_lamp.shadow_soft_size();
156                         light->type = LIGHT_DISTANT;
157                         break;
158                 }
159                 case BL::Lamp::type_AREA: {
160                         BL::AreaLamp b_area_lamp(b_lamp);
161                         light->size = 1.0f;
162                         light->axisu = transform_get_column(&tfm, 0);
163                         light->axisv = transform_get_column(&tfm, 1);
164                         light->sizeu = b_area_lamp.size();
165                         switch(b_area_lamp.shape()) {
166                                 case BL::AreaLamp::shape_SQUARE:
167                                         light->sizev = light->sizeu;
168                                         light->round = false;
169                                         break;
170                                 case BL::AreaLamp::shape_RECTANGLE:
171                                         light->sizev = b_area_lamp.size_y();
172                                         light->round = false;
173                                         break;
174                                 case BL::AreaLamp::shape_DISK:
175                                         light->sizev = light->sizeu;
176                                         light->round = true;
177                                         break;
178                                 case BL::AreaLamp::shape_ELLIPSE:
179                                         light->sizev = b_area_lamp.size_y();
180                                         light->round = true;
181                                         break;
182                         }
183                         light->type = LIGHT_AREA;
184                         break;
185                 }
186         }
187
188         /* location and (inverted!) direction */
189         light->co = transform_get_column(&tfm, 3);
190         light->dir = -transform_get_column(&tfm, 2);
191         light->tfm = tfm;
192
193         /* shader */
194         vector<Shader*> used_shaders;
195         find_shader(b_lamp, used_shaders, scene->default_light);
196         light->shader = used_shaders[0];
197
198         /* shadow */
199         PointerRNA cscene = RNA_pointer_get(&b_scene.ptr, "cycles");
200         PointerRNA clamp = RNA_pointer_get(&b_lamp.ptr, "cycles");
201         light->cast_shadow = get_boolean(clamp, "cast_shadow");
202         light->use_mis = get_boolean(clamp, "use_multiple_importance_sampling");
203         
204         int samples = get_int(clamp, "samples");
205         if(get_boolean(cscene, "use_square_samples"))
206                 light->samples = samples * samples;
207         else
208                 light->samples = samples;
209
210         light->max_bounces = get_int(clamp, "max_bounces");
211
212         if(b_ob != b_ob_instance) {
213                 light->random_id = random_id;
214         }
215         else {
216                 light->random_id = hash_int_2d(hash_string(b_ob.name().c_str()), 0);
217         }
218
219         if(light->type == LIGHT_AREA)
220                 light->is_portal = get_boolean(clamp, "is_portal");
221         else
222                 light->is_portal = false;
223
224         if(light->is_portal)
225                 *use_portal = true;
226
227         /* visibility */
228         uint visibility = object_ray_visibility(b_ob);
229         light->use_diffuse = (visibility & PATH_RAY_DIFFUSE) != 0;
230         light->use_glossy = (visibility & PATH_RAY_GLOSSY) != 0;
231         light->use_transmission = (visibility & PATH_RAY_TRANSMIT) != 0;
232         light->use_scatter = (visibility & PATH_RAY_VOLUME_SCATTER) != 0;
233
234         /* tag */
235         light->tag_update(scene);
236 }
237
238 void BlenderSync::sync_background_light(bool use_portal)
239 {
240         BL::World b_world = b_scene.world();
241
242         if(b_world) {
243                 PointerRNA cscene = RNA_pointer_get(&b_scene.ptr, "cycles");
244                 PointerRNA cworld = RNA_pointer_get(&b_world.ptr, "cycles");
245                 bool sample_as_light = get_boolean(cworld, "sample_as_light");
246
247                 if(sample_as_light || use_portal) {
248                         /* test if we need to sync */
249                         Light *light;
250                         ObjectKey key(b_world, 0, b_world);
251
252                         if(light_map.sync(&light, b_world, b_world, key) ||
253                             world_recalc ||
254                             b_world.ptr.data != world_map)
255                         {
256                                 light->type = LIGHT_BACKGROUND;
257                                 light->map_resolution  = get_int(cworld, "sample_map_resolution");
258                                 light->shader = scene->default_background;
259                                 light->use_mis = sample_as_light;
260                                 light->max_bounces = get_int(cworld, "max_bounces");
261
262                                 int samples = get_int(cworld, "samples");
263                                 if(get_boolean(cscene, "use_square_samples"))
264                                         light->samples = samples * samples;
265                                 else
266                                         light->samples = samples;
267
268                                 light->tag_update(scene);
269                                 light_map.set_recalc(b_world);
270                         }
271                 }
272         }
273
274         world_map = b_world.ptr.data;
275         world_recalc = false;
276 }
277
278 /* Object */
279
280 Object *BlenderSync::sync_object(BL::Depsgraph& b_depsgraph,
281                                  BL::DepsgraphObjectInstance& b_instance,
282                                  uint layer_flag,
283                                  float motion_time,
284                                  bool hide_tris,
285                                  BlenderObjectCulling& culling,
286                                  bool *use_portal)
287 {
288         const bool is_instance = b_instance.is_instance();
289         BL::Object b_ob = b_instance.object();
290         BL::Object b_parent = is_instance ? b_instance.parent()
291                                           : b_instance.object();
292         BL::Object b_ob_instance = is_instance ? b_instance.instance_object()
293                                                : b_ob;
294         const bool motion = motion_time != 0.0f;
295         /*const*/ Transform tfm = get_transform(b_ob.matrix_world());
296         int *persistent_id = NULL;
297         BL::Array<int, OBJECT_PERSISTENT_ID_SIZE> persistent_id_array;
298         if(is_instance) {
299                 persistent_id_array = b_instance.persistent_id();
300                 persistent_id = persistent_id_array.data;
301         }
302
303         /* light is handled separately */
304         if(object_is_light(b_ob)) {
305                 /* don't use lamps for excluded layers used as mask layer */
306                 if(!motion && !((layer_flag & view_layer.holdout_layer) &&
307                                 (layer_flag & view_layer.exclude_layer)))
308                 {
309                         sync_light(b_parent,
310                                    persistent_id,
311                                    b_ob,
312                                    b_ob_instance,
313                                    is_instance ? b_instance.random_id() : 0,
314                                    tfm,
315                                    use_portal);
316                 }
317
318                 return NULL;
319         }
320
321         /* only interested in object that we can create meshes from */
322         if(!object_is_mesh(b_ob)) {
323                 return NULL;
324         }
325
326         /* Perform object culling. */
327         if(culling.test(scene, b_ob, tfm)) {
328                 return NULL;
329         }
330
331         /* Visibility flags for both parent and child. */
332         PointerRNA cobject = RNA_pointer_get(&b_ob.ptr, "cycles");
333         bool use_holdout = (layer_flag & view_layer.holdout_layer) != 0 ||
334                            get_boolean(cobject, "is_holdout");
335         uint visibility = object_ray_visibility(b_ob) & PATH_RAY_ALL_VISIBILITY;
336
337         if(b_parent.ptr.data != b_ob.ptr.data) {
338                 visibility &= object_ray_visibility(b_parent);
339         }
340
341         /* Make holdout objects on excluded layer invisible for non-camera rays. */
342         if(use_holdout && (layer_flag & view_layer.exclude_layer)) {
343                 visibility &= ~(PATH_RAY_ALL_VISIBILITY - PATH_RAY_CAMERA);
344         }
345
346         /* Hide objects not on render layer from camera rays. */
347         if(!(layer_flag & view_layer.layer)) {
348                 visibility &= ~PATH_RAY_CAMERA;
349         }
350
351         /* Don't export completely invisible objects. */
352         if(visibility == 0) {
353                 return NULL;
354         }
355
356         /* key to lookup object */
357         ObjectKey key(b_parent, persistent_id, b_ob_instance);
358         Object *object;
359
360         /* motion vector case */
361         if(motion) {
362                 object = object_map.find(key);
363
364                 if(object && object->use_motion()) {
365                         /* Set transform at matching motion time step. */
366                         int time_index = object->motion_step(motion_time);
367                         if(time_index >= 0) {
368                                 object->motion[time_index] = tfm;
369                         }
370
371                         /* mesh deformation */
372                         if(object->mesh)
373                                 sync_mesh_motion(b_depsgraph, b_ob, object, motion_time);
374                 }
375
376                 return object;
377         }
378
379         /* test if we need to sync */
380         bool object_updated = false;
381
382         if(object_map.sync(&object, b_ob, b_parent, key))
383                 object_updated = true;
384         
385         /* mesh sync */
386         object->mesh = sync_mesh(b_depsgraph, b_ob, b_ob_instance, object_updated, hide_tris);
387
388         /* special case not tracked by object update flags */
389
390         /* holdout */
391         if(use_holdout != object->use_holdout) {
392                 object->use_holdout = use_holdout;
393                 scene->object_manager->tag_update(scene);
394                 object_updated = true;
395         }
396
397         if(visibility != object->visibility) {
398                 object->visibility = visibility;
399                 object_updated = true;
400         }
401
402         bool is_shadow_catcher = get_boolean(cobject, "is_shadow_catcher");
403         if(is_shadow_catcher != object->is_shadow_catcher) {
404                 object->is_shadow_catcher = is_shadow_catcher;
405                 object_updated = true;
406         }
407
408         /* object sync
409          * transform comparison should not be needed, but duplis don't work perfect
410          * in the depsgraph and may not signal changes, so this is a workaround */
411         if(object_updated || (object->mesh && object->mesh->need_update) || tfm != object->tfm) {
412                 object->name = b_ob.name().c_str();
413                 object->pass_id = b_ob.pass_index();
414                 object->tfm = tfm;
415                 object->motion.clear();
416
417                 /* motion blur */
418                 Scene::MotionType need_motion = scene->need_motion();
419                 if(need_motion != Scene::MOTION_NONE && object->mesh) {
420                         Mesh *mesh = object->mesh;
421                         mesh->use_motion_blur = false;
422                         mesh->motion_steps = 0;
423
424                         uint motion_steps;
425
426                         if(scene->need_motion() == Scene::MOTION_BLUR) {
427                                 motion_steps = object_motion_steps(b_parent, b_ob);
428                                 if(motion_steps && object_use_deform_motion(b_parent, b_ob)) {
429                                         mesh->motion_steps = motion_steps;
430                                         mesh->use_motion_blur = true;
431                                 }
432                         }
433                         else {
434                                 motion_steps = 3;
435                                 mesh->motion_steps = motion_steps;
436                         }
437
438                         object->motion.resize(motion_steps, transform_empty());
439
440                         if(motion_steps) {
441                                 object->motion[motion_steps/2] = tfm;
442
443                                 for(size_t step = 0; step < motion_steps; step++) {
444                                         motion_times.insert(object->motion_time(step));
445                                 }
446                         }
447                 }
448
449                 /* dupli texture coordinates and random_id */
450                 if(is_instance) {
451                         object->dupli_generated = 0.5f*get_float3(b_instance.orco()) - make_float3(0.5f, 0.5f, 0.5f);
452                         object->dupli_uv = get_float2(b_instance.uv());
453                         object->random_id = b_instance.random_id();
454
455                         /* Sync possible particle data. */
456                         sync_dupli_particle(b_ob, b_instance, object);
457                 }
458                 else {
459                         object->dupli_generated = make_float3(0.0f, 0.0f, 0.0f);
460                         object->dupli_uv = make_float2(0.0f, 0.0f);
461                         object->random_id =  hash_int_2d(hash_string(object->name.c_str()), 0);
462                 }
463
464                 object->tag_update(scene);
465         }
466
467         return object;
468 }
469
470 static bool object_render_hide_original(BL::Object::type_enum ob_type,
471                                         BL::Object::dupli_type_enum dupli_type)
472 {
473         /* metaball exception, they duplicate self */
474         if(ob_type == BL::Object::type_META)
475                 return false;
476
477         return (dupli_type == BL::Object::dupli_type_VERTS ||
478                 dupli_type == BL::Object::dupli_type_FACES ||
479                 dupli_type == BL::Object::dupli_type_FRAMES);
480 }
481
482 static bool object_render_hide(BL::Object& b_ob,
483                                bool top_level,
484                                bool parent_hide,
485                                bool& hide_triangles,
486                                BL::Depsgraph::mode_enum depsgraph_mode)
487 {
488         /* check if we should render or hide particle emitter */
489         BL::Object::particle_systems_iterator b_psys;
490
491         bool hair_present = false;
492         bool has_particles = false;
493         bool show_emitter = false;
494         bool hide_emitter = false;
495         bool hide_as_dupli_parent = false;
496         bool hide_as_dupli_child_original = false;
497
498         for(b_ob.particle_systems.begin(b_psys); b_psys != b_ob.particle_systems.end(); ++b_psys) {
499                 if((b_psys->settings().render_type() == BL::ParticleSettings::render_type_PATH) &&
500                    (b_psys->settings().type()==BL::ParticleSettings::type_HAIR))
501                         hair_present = true;
502                 has_particles = true;
503         }
504
505         /* Both mode_PREVIEW and mode_VIEWPORT are treated the same here.*/
506         const bool show_duplicator = depsgraph_mode == BL::Depsgraph::mode_RENDER
507                                      ? b_ob.show_duplicator_for_render()
508                                      : b_ob.show_duplicator_for_viewport();
509
510         if(has_particles) {
511                 show_emitter = show_duplicator;
512                 hide_emitter = !show_emitter;
513         } else if(b_ob.is_duplicator()) {
514                 if(top_level || show_duplicator) {
515                         hide_as_dupli_parent = true;
516                 }
517         }
518
519         /* hide original object for duplis */
520         BL::Object parent = b_ob.parent();
521         while(parent) {
522                 if(object_render_hide_original(b_ob.type(),
523                                                parent.dupli_type()))
524                 {
525                         if(parent_hide) {
526                                 hide_as_dupli_child_original = true;
527                                 break;
528                         }
529                 }
530                 parent = parent.parent();
531         }
532         
533         hide_triangles = hide_emitter;
534
535         if(show_emitter) {
536                 return false;
537         }
538         else if(hair_present) {
539                 return hide_as_dupli_child_original;
540         }
541         else {
542                 return (hide_as_dupli_parent || hide_as_dupli_child_original);
543         }
544 }
545
546 /* Object Loop */
547
548 void BlenderSync::sync_objects(BL::Depsgraph& b_depsgraph, float motion_time)
549 {
550         /* layer data */
551         bool motion = motion_time != 0.0f;
552         
553         if(!motion) {
554                 /* prepare for sync */
555                 light_map.pre_sync();
556                 mesh_map.pre_sync();
557                 object_map.pre_sync();
558                 particle_system_map.pre_sync();
559                 motion_times.clear();
560         }
561         else {
562                 mesh_motion_synced.clear();
563         }
564
565         /* initialize culling */
566         BlenderObjectCulling culling(scene, b_scene);
567
568         /* object loop */
569         bool cancel = false;
570         bool use_portal = false;
571
572         BL::Depsgraph::mode_enum depsgraph_mode = b_depsgraph.mode();
573
574         BL::Depsgraph::object_instances_iterator b_instance_iter;
575         for(b_depsgraph.object_instances.begin(b_instance_iter);
576             b_instance_iter != b_depsgraph.object_instances.end() && !cancel;
577             ++b_instance_iter)
578         {
579                 BL::DepsgraphObjectInstance b_instance = *b_instance_iter;
580                 BL::Object b_ob = b_instance.object();
581                 if(!b_ob.is_visible()) {
582                         continue;
583                 }
584
585                 progress.set_sync_status("Synchronizing object", b_ob.name());
586
587                 /* load per-object culling data */
588                 culling.init_object(scene, b_ob);
589
590                 /* test if object needs to be hidden */
591                 bool hide_tris;
592
593                  if(!object_render_hide(b_ob, true, true, hide_tris, depsgraph_mode)) {
594                         /* object itself */
595                         sync_object(b_depsgraph,
596                                     b_instance,
597                                     ~(0), /* until we get rid of layers */
598                                     motion_time,
599                                     hide_tris,
600                                     culling,
601                                     &use_portal);
602                  }
603
604                 cancel = progress.get_cancel();
605         }
606
607         progress.set_sync_status("");
608
609         if(!cancel && !motion) {
610                 sync_background_light(use_portal);
611
612                 /* handle removed data and modified pointers */
613                 if(light_map.post_sync())
614                         scene->light_manager->tag_update(scene);
615                 if(mesh_map.post_sync())
616                         scene->mesh_manager->tag_update(scene);
617                 if(object_map.post_sync())
618                         scene->object_manager->tag_update(scene);
619                 if(particle_system_map.post_sync())
620                         scene->particle_system_manager->tag_update(scene);
621         }
622
623         if(motion)
624                 mesh_motion_synced.clear();
625 }
626
627 void BlenderSync::sync_motion(BL::RenderSettings& b_render,
628                               BL::Depsgraph& b_depsgraph,
629                               BL::Object& b_override,
630                               int width, int height,
631                               void **python_thread_state)
632 {
633         if(scene->need_motion() == Scene::MOTION_NONE)
634                 return;
635
636         /* get camera object here to deal with camera switch */
637         BL::Object b_cam = b_scene.camera();
638         if(b_override)
639                 b_cam = b_override;
640
641         Camera prevcam = *(scene->camera);
642
643         int frame_center = b_scene.frame_current();
644         float subframe_center = b_scene.frame_subframe();
645         float frame_center_delta = 0.0f;
646
647         if(scene->need_motion() != Scene::MOTION_PASS &&
648            scene->camera->motion_position != Camera::MOTION_POSITION_CENTER)
649         {
650                 float shuttertime = scene->camera->shuttertime;
651                 if(scene->camera->motion_position == Camera::MOTION_POSITION_END) {
652                         frame_center_delta = -shuttertime * 0.5f;
653                 }
654                 else {
655                         assert(scene->camera->motion_position == Camera::MOTION_POSITION_START);
656                         frame_center_delta = shuttertime * 0.5f;
657                 }
658
659                 float time = frame_center + subframe_center + frame_center_delta;
660                 int frame = (int)floorf(time);
661                 float subframe = time - frame;
662                 python_thread_state_restore(python_thread_state);
663                 b_engine.frame_set(frame, subframe);
664                 python_thread_state_save(python_thread_state);
665                 sync_camera_motion(b_render, b_cam, width, height, 0.0f);
666                 sync_objects(b_depsgraph, 0.0f);
667         }
668
669         /* always sample these times for camera motion */
670         motion_times.insert(-1.0f);
671         motion_times.insert(1.0f);
672
673         /* note iteration over motion_times set happens in sorted order */
674         foreach(float relative_time, motion_times) {
675                 /* center time is already handled. */
676                 if(relative_time == 0.0f) {
677                         continue;
678                 }
679
680                 VLOG(1) << "Synchronizing motion for the relative time "
681                         << relative_time << ".";
682
683                 /* fixed shutter time to get previous and next frame for motion pass */
684                 float shuttertime = scene->motion_shutter_time();
685
686                 /* compute frame and subframe time */
687                 float time = frame_center + subframe_center + frame_center_delta + relative_time * shuttertime * 0.5f;
688                 int frame = (int)floorf(time);
689                 float subframe = time - frame;
690
691                 /* change frame */
692                 python_thread_state_restore(python_thread_state);
693                 b_engine.frame_set(frame, subframe);
694                 python_thread_state_save(python_thread_state);
695
696                 /* sync camera, only supports two times at the moment */
697                 if(relative_time == -1.0f || relative_time == 1.0f) {
698                         sync_camera_motion(b_render,
699                                            b_cam,
700                                            width, height,
701                                            relative_time);
702                 }
703
704                 /* sync object */
705                 sync_objects(b_depsgraph, relative_time);
706         }
707
708         /* we need to set the python thread state again because this
709          * function assumes it is being executed from python and will
710          * try to save the thread state */
711         python_thread_state_restore(python_thread_state);
712         b_engine.frame_set(frame_center, subframe_center);
713         python_thread_state_save(python_thread_state);
714
715         /* tag camera for motion update */
716         if(scene->camera->motion_modified(prevcam))
717                 scene->camera->tag_update();
718 }
719
720 CCL_NAMESPACE_END
721