Merge branch 'master' into blender2.8
[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                         if(b_area_lamp.shape() == BL::AreaLamp::shape_RECTANGLE)
166                                 light->sizev = b_area_lamp.size_y();
167                         else
168                                 light->sizev = light->sizeu;
169                         light->type = LIGHT_AREA;
170                         break;
171                 }
172         }
173
174         /* location and (inverted!) direction */
175         light->co = transform_get_column(&tfm, 3);
176         light->dir = -transform_get_column(&tfm, 2);
177         light->tfm = tfm;
178
179         /* shader */
180         vector<Shader*> used_shaders;
181         find_shader(b_lamp, used_shaders, scene->default_light);
182         light->shader = used_shaders[0];
183
184         /* shadow */
185         PointerRNA cscene = RNA_pointer_get(&b_scene.ptr, "cycles");
186         PointerRNA clamp = RNA_pointer_get(&b_lamp.ptr, "cycles");
187         light->cast_shadow = get_boolean(clamp, "cast_shadow");
188         light->use_mis = get_boolean(clamp, "use_multiple_importance_sampling");
189         
190         int samples = get_int(clamp, "samples");
191         if(get_boolean(cscene, "use_square_samples"))
192                 light->samples = samples * samples;
193         else
194                 light->samples = samples;
195
196         light->max_bounces = get_int(clamp, "max_bounces");
197
198         if(b_ob != b_ob_instance) {
199                 light->random_id = random_id;
200         }
201         else {
202                 light->random_id = hash_int_2d(hash_string(b_ob.name().c_str()), 0);
203         }
204
205         if(light->type == LIGHT_AREA)
206                 light->is_portal = get_boolean(clamp, "is_portal");
207         else
208                 light->is_portal = false;
209
210         if(light->is_portal)
211                 *use_portal = true;
212
213         /* visibility */
214         uint visibility = object_ray_visibility(b_ob);
215         light->use_diffuse = (visibility & PATH_RAY_DIFFUSE) != 0;
216         light->use_glossy = (visibility & PATH_RAY_GLOSSY) != 0;
217         light->use_transmission = (visibility & PATH_RAY_TRANSMIT) != 0;
218         light->use_scatter = (visibility & PATH_RAY_VOLUME_SCATTER) != 0;
219
220         /* tag */
221         light->tag_update(scene);
222 }
223
224 void BlenderSync::sync_background_light(bool use_portal)
225 {
226         BL::World b_world = b_scene.world();
227
228         if(b_world) {
229                 PointerRNA cscene = RNA_pointer_get(&b_scene.ptr, "cycles");
230                 PointerRNA cworld = RNA_pointer_get(&b_world.ptr, "cycles");
231                 bool sample_as_light = get_boolean(cworld, "sample_as_light");
232
233                 if(sample_as_light || use_portal) {
234                         /* test if we need to sync */
235                         Light *light;
236                         ObjectKey key(b_world, 0, b_world);
237
238                         if(light_map.sync(&light, b_world, b_world, key) ||
239                             world_recalc ||
240                             b_world.ptr.data != world_map)
241                         {
242                                 light->type = LIGHT_BACKGROUND;
243                                 light->map_resolution  = get_int(cworld, "sample_map_resolution");
244                                 light->shader = scene->default_background;
245                                 light->use_mis = sample_as_light;
246                                 light->max_bounces = get_int(cworld, "max_bounces");
247
248                                 int samples = get_int(cworld, "samples");
249                                 if(get_boolean(cscene, "use_square_samples"))
250                                         light->samples = samples * samples;
251                                 else
252                                         light->samples = samples;
253
254                                 light->tag_update(scene);
255                                 light_map.set_recalc(b_world);
256                         }
257                 }
258         }
259
260         world_map = b_world.ptr.data;
261         world_recalc = false;
262 }
263
264 /* Object */
265
266 Object *BlenderSync::sync_object(BL::Depsgraph& b_depsgraph,
267                                  BL::Depsgraph::duplis_iterator& b_dupli_iter,
268                                  uint layer_flag,
269                                  float motion_time,
270                                  bool hide_tris,
271                                  BlenderObjectCulling& culling,
272                                  bool *use_portal)
273 {
274         const bool is_instance = b_dupli_iter->is_instance();
275         BL::Object b_ob = b_dupli_iter->object();
276         BL::Object b_parent = is_instance ? b_dupli_iter->parent()
277                                           : b_dupli_iter->object();
278         BL::Object b_ob_instance = is_instance ? b_dupli_iter->instance_object()
279                                                : b_ob;
280         const bool motion = motion_time != 0.0f;
281         /*const*/ Transform tfm = get_transform(b_ob.matrix_world());
282         int *persistent_id = NULL;
283         BL::Array<int, OBJECT_PERSISTENT_ID_SIZE> persistent_id_array;
284         if(is_instance) {
285                 persistent_id_array = b_dupli_iter->persistent_id();
286                 persistent_id = persistent_id_array.data;
287         }
288
289         /* light is handled separately */
290         if(object_is_light(b_ob)) {
291                 /* don't use lamps for excluded layers used as mask layer */
292                 if(!motion && !((layer_flag & view_layer.holdout_layer) &&
293                                 (layer_flag & view_layer.exclude_layer)))
294                 {
295                         sync_light(b_parent,
296                                    persistent_id,
297                                    b_ob,
298                                    b_ob_instance,
299                                    is_instance ? b_dupli_iter->random_id() : 0,
300                                    tfm,
301                                    use_portal);
302                 }
303
304                 return NULL;
305         }
306
307         /* only interested in object that we can create meshes from */
308         if(!object_is_mesh(b_ob)) {
309                 return NULL;
310         }
311
312         /* Perform object culling. */
313         if(culling.test(scene, b_ob, tfm)) {
314                 return NULL;
315         }
316
317         /* Visibility flags for both parent and child. */
318         PointerRNA cobject = RNA_pointer_get(&b_ob.ptr, "cycles");
319         bool use_holdout = (layer_flag & view_layer.holdout_layer) != 0 ||
320                            get_boolean(cobject, "is_holdout");
321         uint visibility = object_ray_visibility(b_ob) & PATH_RAY_ALL_VISIBILITY;
322
323         if(b_parent.ptr.data != b_ob.ptr.data) {
324                 visibility &= object_ray_visibility(b_parent);
325         }
326
327         /* Make holdout objects on excluded layer invisible for non-camera rays. */
328         if(use_holdout && (layer_flag & view_layer.exclude_layer)) {
329                 visibility &= ~(PATH_RAY_ALL_VISIBILITY - PATH_RAY_CAMERA);
330         }
331
332         /* Hide objects not on render layer from camera rays. */
333         if(!(layer_flag & view_layer.layer)) {
334                 visibility &= ~PATH_RAY_CAMERA;
335         }
336
337         /* Don't export completely invisible objects. */
338         if(visibility == 0) {
339                 return NULL;
340         }
341
342         /* key to lookup object */
343         ObjectKey key(b_parent, persistent_id, b_ob_instance);
344         Object *object;
345
346         /* motion vector case */
347         if(motion) {
348                 object = object_map.find(key);
349
350                 if(object && object->use_motion()) {
351                         /* Set transform at matching motion time step. */
352                         int time_index = object->motion_step(motion_time);
353                         if(time_index >= 0) {
354                                 object->motion[time_index] = tfm;
355                         }
356
357                         /* mesh deformation */
358                         if(object->mesh)
359                                 sync_mesh_motion(b_depsgraph, b_ob, object, motion_time);
360                 }
361
362                 return object;
363         }
364
365         /* test if we need to sync */
366         bool object_updated = false;
367
368         if(object_map.sync(&object, b_ob, b_parent, key))
369                 object_updated = true;
370         
371         /* mesh sync */
372         object->mesh = sync_mesh(b_depsgraph, b_ob, b_ob_instance, object_updated, hide_tris);
373
374         /* special case not tracked by object update flags */
375
376         /* holdout */
377         if(use_holdout != object->use_holdout) {
378                 object->use_holdout = use_holdout;
379                 scene->object_manager->tag_update(scene);
380                 object_updated = true;
381         }
382
383         if(visibility != object->visibility) {
384                 object->visibility = visibility;
385                 object_updated = true;
386         }
387
388         bool is_shadow_catcher = get_boolean(cobject, "is_shadow_catcher");
389         if(is_shadow_catcher != object->is_shadow_catcher) {
390                 object->is_shadow_catcher = is_shadow_catcher;
391                 object_updated = true;
392         }
393
394         /* object sync
395          * transform comparison should not be needed, but duplis don't work perfect
396          * in the depsgraph and may not signal changes, so this is a workaround */
397         if(object_updated || (object->mesh && object->mesh->need_update) || tfm != object->tfm) {
398                 object->name = b_ob.name().c_str();
399                 object->pass_id = b_ob.pass_index();
400                 object->tfm = tfm;
401                 object->motion.clear();
402
403                 /* motion blur */
404                 Scene::MotionType need_motion = scene->need_motion();
405                 if(need_motion != Scene::MOTION_NONE && object->mesh) {
406                         Mesh *mesh = object->mesh;
407                         mesh->use_motion_blur = false;
408                         mesh->motion_steps = 0;
409
410                         uint motion_steps;
411
412                         if(scene->need_motion() == Scene::MOTION_BLUR) {
413                                 motion_steps = object_motion_steps(b_parent, b_ob);
414                                 if(object_use_deform_motion(b_parent, b_ob)) {
415                                         mesh->motion_steps = motion_steps;
416                                         mesh->use_motion_blur = true;
417                                 }
418                         }
419                         else {
420                                 motion_steps = 3;
421                                 mesh->motion_steps = motion_steps;
422                         }
423
424                         object->motion.resize(motion_steps, transform_empty());
425                         object->motion[motion_steps/2] = tfm;
426
427                         for(size_t step = 0; step < motion_steps; step++) {
428                                 motion_times.insert(object->motion_time(step));
429                         }
430                 }
431
432                 /* dupli texture coordinates and random_id */
433                 if(is_instance) {
434                         object->dupli_generated = 0.5f*get_float3(b_dupli_iter->orco()) - make_float3(0.5f, 0.5f, 0.5f);
435                         object->dupli_uv = get_float2(b_dupli_iter->uv());
436                         object->random_id = b_dupli_iter->random_id();
437                 }
438                 else {
439                         object->dupli_generated = make_float3(0.0f, 0.0f, 0.0f);
440                         object->dupli_uv = make_float2(0.0f, 0.0f);
441                         object->random_id =  hash_int_2d(hash_string(object->name.c_str()), 0);
442                 }
443
444                 object->tag_update(scene);
445         }
446
447         return object;
448 }
449
450 static bool object_render_hide_original(BL::Object::type_enum ob_type,
451                                         BL::Object::dupli_type_enum dupli_type)
452 {
453         /* metaball exception, they duplicate self */
454         if(ob_type == BL::Object::type_META)
455                 return false;
456
457         return (dupli_type == BL::Object::dupli_type_VERTS ||
458                 dupli_type == BL::Object::dupli_type_FACES ||
459                 dupli_type == BL::Object::dupli_type_FRAMES);
460 }
461
462 static bool object_render_hide(BL::Object& b_ob,
463                                bool top_level,
464                                bool parent_hide,
465                                bool& hide_triangles)
466 {
467         /* check if we should render or hide particle emitter */
468         BL::Object::particle_systems_iterator b_psys;
469
470         bool hair_present = false;
471         bool has_particles = false;
472         bool show_emitter = false;
473         bool hide_emitter = false;
474         bool hide_as_dupli_parent = false;
475         bool hide_as_dupli_child_original = false;
476
477         for(b_ob.particle_systems.begin(b_psys); b_psys != b_ob.particle_systems.end(); ++b_psys) {
478                 if((b_psys->settings().render_type() == BL::ParticleSettings::render_type_PATH) &&
479                    (b_psys->settings().type()==BL::ParticleSettings::type_HAIR))
480                         hair_present = true;
481                 has_particles = true;
482         }
483
484         if(has_particles) {
485                 show_emitter = b_ob.show_duplicator_for_render();
486                 hide_emitter = !show_emitter;
487         } else if(b_ob.is_duplicator()) {
488                 if(top_level || b_ob.show_duplicator_for_render()) {
489                         hide_as_dupli_parent = true;
490                 }
491         }
492
493         /* hide original object for duplis */
494         BL::Object parent = b_ob.parent();
495         while(parent) {
496                 if(object_render_hide_original(b_ob.type(),
497                                                parent.dupli_type()))
498                 {
499                         if(parent_hide) {
500                                 hide_as_dupli_child_original = true;
501                                 break;
502                         }
503                 }
504                 parent = parent.parent();
505         }
506         
507         hide_triangles = hide_emitter;
508
509         if(show_emitter) {
510                 return false;
511         }
512         else if(hair_present) {
513                 return hide_as_dupli_child_original;
514         }
515         else {
516                 return (hide_as_dupli_parent || hide_as_dupli_child_original);
517         }
518 }
519
520 /* Object Loop */
521
522 void BlenderSync::sync_objects(BL::Depsgraph& b_depsgraph, float motion_time)
523 {
524         /* layer data */
525         bool motion = motion_time != 0.0f;
526         
527         if(!motion) {
528                 /* prepare for sync */
529                 light_map.pre_sync();
530                 mesh_map.pre_sync();
531                 object_map.pre_sync();
532                 particle_system_map.pre_sync();
533                 motion_times.clear();
534         }
535         else {
536                 mesh_motion_synced.clear();
537         }
538
539         /* initialize culling */
540         BlenderObjectCulling culling(scene, b_scene);
541
542         /* object loop */
543         bool cancel = false;
544         bool use_portal = false;
545
546         BL::Depsgraph::duplis_iterator b_dupli_iter;
547         for(b_depsgraph.duplis.begin(b_dupli_iter);
548             b_dupli_iter != b_depsgraph.duplis.end() && !cancel;
549             ++b_dupli_iter)
550         {
551                 BL::Object b_ob = b_dupli_iter->object();
552                 if(!b_ob.is_visible()) {
553                         continue;
554                 }
555
556                 progress.set_sync_status("Synchronizing object", b_ob.name());
557
558                 /* load per-object culling data */
559                 culling.init_object(scene, b_ob);
560
561                 /* test if object needs to be hidden */
562                 bool hide_tris;
563
564                  if(!object_render_hide(b_ob, true, true, hide_tris)) {
565                         /* object itself */
566                         sync_object(b_depsgraph,
567                                     b_dupli_iter,
568                                     ~(0), /* until we get rid of layers */
569                                     motion_time,
570                                     hide_tris,
571                                     culling,
572                                     &use_portal);
573                  }
574
575                 cancel = progress.get_cancel();
576         }
577
578         progress.set_sync_status("");
579
580         if(!cancel && !motion) {
581                 sync_background_light(use_portal);
582
583                 /* handle removed data and modified pointers */
584                 if(light_map.post_sync())
585                         scene->light_manager->tag_update(scene);
586                 if(mesh_map.post_sync())
587                         scene->mesh_manager->tag_update(scene);
588                 if(object_map.post_sync())
589                         scene->object_manager->tag_update(scene);
590                 if(particle_system_map.post_sync())
591                         scene->particle_system_manager->tag_update(scene);
592         }
593
594         if(motion)
595                 mesh_motion_synced.clear();
596 }
597
598 void BlenderSync::sync_motion(BL::RenderSettings& b_render,
599                               BL::Depsgraph& b_depsgraph,
600                               BL::Object& b_override,
601                               int width, int height,
602                               void **python_thread_state)
603 {
604         if(scene->need_motion() == Scene::MOTION_NONE)
605                 return;
606
607         /* get camera object here to deal with camera switch */
608         BL::Object b_cam = b_scene.camera();
609         if(b_override)
610                 b_cam = b_override;
611
612         Camera prevcam = *(scene->camera);
613
614         int frame_center = b_scene.frame_current();
615         float subframe_center = b_scene.frame_subframe();
616         float frame_center_delta = 0.0f;
617
618         if(scene->need_motion() != Scene::MOTION_PASS &&
619            scene->camera->motion_position != Camera::MOTION_POSITION_CENTER)
620         {
621                 float shuttertime = scene->camera->shuttertime;
622                 if(scene->camera->motion_position == Camera::MOTION_POSITION_END) {
623                         frame_center_delta = -shuttertime * 0.5f;
624                 }
625                 else {
626                         assert(scene->camera->motion_position == Camera::MOTION_POSITION_START);
627                         frame_center_delta = shuttertime * 0.5f;
628                 }
629
630                 /* TODO: move frame on depsgraph. */
631                 float time = frame_center + subframe_center + frame_center_delta;
632                 int frame = (int)floorf(time);
633                 float subframe = time - frame;
634                 python_thread_state_restore(python_thread_state);
635                 b_engine.frame_set(frame, subframe);
636                 python_thread_state_save(python_thread_state);
637                 sync_camera_motion(b_render, b_cam, width, height, 0.0f);
638                 sync_objects(b_depsgraph, 0.0f);
639         }
640
641         /* always sample these times for camera motion */
642         motion_times.insert(-1.0f);
643         motion_times.insert(1.0f);
644
645         /* note iteration over motion_times set happens in sorted order */
646         foreach(float relative_time, motion_times) {
647                 /* center time is already handled. */
648                 if(relative_time == 0.0f) {
649                         continue;
650                 }
651
652                 VLOG(1) << "Synchronizing motion for the relative time "
653                         << relative_time << ".";
654
655                 /* fixed shutter time to get previous and next frame for motion pass */
656                 float shuttertime = scene->motion_shutter_time();
657
658                 /* compute frame and subframe time */
659                 float time = frame_center + subframe_center + frame_center_delta + relative_time * shuttertime * 0.5f;
660                 int frame = (int)floorf(time);
661                 float subframe = time - frame;
662
663                 /* TODO: move frame on depsgraph. */
664                 /* change frame */
665                 python_thread_state_restore(python_thread_state);
666                 b_engine.frame_set(frame, subframe);
667                 python_thread_state_save(python_thread_state);
668
669                 /* sync camera, only supports two times at the moment */
670                 if(relative_time == -1.0f || relative_time == 1.0f) {
671                         sync_camera_motion(b_render,
672                                            b_cam,
673                                            width, height,
674                                            relative_time);
675                 }
676
677                 /* sync object */
678                 sync_objects(b_depsgraph, relative_time);
679         }
680
681         /* we need to set the python thread state again because this
682          * function assumes it is being executed from python and will
683          * try to save the thread state */
684         python_thread_state_restore(python_thread_state);
685         b_engine.frame_set(frame_center, subframe_center);
686         python_thread_state_save(python_thread_state);
687
688         /* tag camera for motion update */
689         if(scene->camera->motion_modified(prevcam))
690                 scene->camera->tag_update();
691 }
692
693 CCL_NAMESPACE_END
694