Split Normals I (2/5): Add basic BMesh support of split normals.
[blender.git] / source / blender / modifiers / intern / MOD_array.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software  Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2005 by the Blender Foundation.
19  * All rights reserved.
20  *
21  * Contributor(s): Daniel Dunbar
22  *                 Ton Roosendaal,
23  *                 Ben Batt,
24  *                 Brecht Van Lommel,
25  *                 Campbell Barton
26  *
27  * ***** END GPL LICENSE BLOCK *****
28  *
29  */
30
31 /** \file blender/modifiers/intern/MOD_array.c
32  *  \ingroup modifiers
33  */
34
35
36 /* Array modifier: duplicates the object multiple times along an axis */
37
38 #include "MEM_guardedalloc.h"
39
40 #include "BLI_math.h"
41 #include "BLI_utildefines.h"
42 #include "BLI_string.h"
43 #include "BLI_ghash.h"
44 #include "BLI_edgehash.h"
45
46 #include "DNA_curve_types.h"
47 #include "DNA_meshdata_types.h"
48 #include "DNA_object_types.h"
49 #include "DNA_scene_types.h"
50
51 #include "BKE_cdderivedmesh.h"
52 #include "BKE_displist.h"
53 #include "BKE_curve.h"
54 #include "BKE_mesh.h"
55 #include "BKE_modifier.h"
56 #include "BKE_object.h"
57
58 #include "MOD_util.h"
59
60 #include "bmesh.h"
61
62 #include "depsgraph_private.h"
63
64 #include <ctype.h>
65 #include <stdlib.h>
66 #include <string.h>
67
68 /* Due to cyclic dependencies it's possible that curve used for
69  * deformation here is not evaluated at the time of evaluating
70  * this modifier.
71  */
72 #define CYCLIC_DEPENDENCY_WORKAROUND
73
74 static void initData(ModifierData *md)
75 {
76         ArrayModifierData *amd = (ArrayModifierData *) md;
77
78         /* default to 2 duplicates distributed along the x-axis by an
79          * offset of 1 object-width
80          */
81         amd->start_cap = amd->end_cap = amd->curve_ob = amd->offset_ob = NULL;
82         amd->count = 2;
83         zero_v3(amd->offset);
84         amd->scale[0] = 1;
85         amd->scale[1] = amd->scale[2] = 0;
86         amd->length = 0;
87         amd->merge_dist = 0.01;
88         amd->fit_type = MOD_ARR_FIXEDCOUNT;
89         amd->offset_type = MOD_ARR_OFF_RELATIVE;
90         amd->flags = 0;
91 }
92
93 static void copyData(ModifierData *md, ModifierData *target)
94 {
95 #if 0
96         ArrayModifierData *amd = (ArrayModifierData *) md;
97         ArrayModifierData *tamd = (ArrayModifierData *) target;
98 #endif
99         modifier_copyData_generic(md, target);
100 }
101
102 static void foreachObjectLink(
103         ModifierData *md, Object *ob,
104         void (*walk)(void *userData, Object *ob, Object **obpoin),
105         void *userData)
106 {
107         ArrayModifierData *amd = (ArrayModifierData *) md;
108
109         walk(userData, ob, &amd->start_cap);
110         walk(userData, ob, &amd->end_cap);
111         walk(userData, ob, &amd->curve_ob);
112         walk(userData, ob, &amd->offset_ob);
113 }
114
115 static void updateDepgraph(ModifierData *md, DagForest *forest,
116                            struct Scene *UNUSED(scene), Object *UNUSED(ob), DagNode *obNode)
117 {
118         ArrayModifierData *amd = (ArrayModifierData *) md;
119
120         if (amd->start_cap) {
121                 DagNode *curNode = dag_get_node(forest, amd->start_cap);
122
123                 dag_add_relation(forest, curNode, obNode,
124                                  DAG_RL_DATA_DATA | DAG_RL_OB_DATA, "Array Modifier");
125         }
126         if (amd->end_cap) {
127                 DagNode *curNode = dag_get_node(forest, amd->end_cap);
128
129                 dag_add_relation(forest, curNode, obNode,
130                                  DAG_RL_DATA_DATA | DAG_RL_OB_DATA, "Array Modifier");
131         }
132         if (amd->curve_ob) {
133                 DagNode *curNode = dag_get_node(forest, amd->curve_ob);
134                 curNode->eval_flags |= DAG_EVAL_NEED_CURVE_PATH;
135
136                 dag_add_relation(forest, curNode, obNode,
137                                  DAG_RL_DATA_DATA | DAG_RL_OB_DATA, "Array Modifier");
138         }
139         if (amd->offset_ob) {
140                 DagNode *curNode = dag_get_node(forest, amd->offset_ob);
141
142                 dag_add_relation(forest, curNode, obNode,
143                                  DAG_RL_DATA_DATA | DAG_RL_OB_DATA, "Array Modifier");
144         }
145 }
146
147 static float vertarray_size(MVert *mvert, int numVerts, int axis)
148 {
149         int i;
150         float min_co, max_co;
151
152         /* if there are no vertices, width is 0 */
153         if (numVerts == 0) return 0;
154
155         /* find the minimum and maximum coordinates on the desired axis */
156         min_co = max_co = mvert->co[axis];
157         mvert++;
158         for (i = 1; i < numVerts; ++i, ++mvert) {
159                 if (mvert->co[axis] < min_co) min_co = mvert->co[axis];
160                 if (mvert->co[axis] > max_co) max_co = mvert->co[axis];
161         }
162
163         return max_co - min_co;
164 }
165
166 static int *find_doubles_index_map(BMesh *bm, BMOperator *dupe_op,
167                                    const ArrayModifierData *amd,
168                                    int *index_map_length)
169 {
170         BMOperator find_op;
171         BMOIter oiter;
172         BMVert *v, *v2;
173         BMElem *ele;
174         int *index_map, i;
175
176         BMO_op_initf(bm, &find_op, (BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE),
177                      "find_doubles verts=%av dist=%f keep_verts=%s",
178                      amd->merge_dist, dupe_op, "geom");
179
180         BMO_op_exec(bm, &find_op);
181
182         i = 0;
183         BMO_ITER (ele, &oiter, dupe_op->slots_in, "geom", BM_ALL) {
184                 BM_elem_index_set(ele, i); /* set_dirty */
185                 i++;
186         }
187
188         BMO_ITER (ele, &oiter, dupe_op->slots_out, "geom.out", BM_ALL) {
189                 BM_elem_index_set(ele, i); /* set_dirty */
190                 i++;
191         }
192         /* above loops over all, so set all to dirty, if this is somehow
193          * setting valid values, this line can be removed - campbell */
194         bm->elem_index_dirty |= BM_ALL;
195
196         (*index_map_length) = i;
197         index_map = MEM_callocN(sizeof(int) * (*index_map_length), "index_map");
198
199         /*element type argument doesn't do anything here*/
200         BMO_ITER (v, &oiter, find_op.slots_out, "targetmap.out", 0) {
201                 v2 = BMO_iter_map_value_ptr(&oiter);
202
203                 index_map[BM_elem_index_get(v)] = BM_elem_index_get(v2) + 1;
204         }
205
206         BMO_op_finish(bm, &find_op);
207
208         return index_map;
209 }
210
211 /* Used for start/end cap.
212  *
213  * this function expects all existing vertices to be tagged,
214  * so we can know new verts are not tagged.
215  *
216  * All verts will be tagged on exit.
217  */
218 static void bm_merge_dm_transform(BMesh *bm, DerivedMesh *dm, float mat[4][4],
219                                   const ArrayModifierData *amd,
220                                   BMOperator *dupe_op,
221                                   BMOpSlot dupe_op_slot_args[BMO_OP_MAX_SLOTS], const char *dupe_slot_name,
222                                   BMOperator *weld_op)
223 {
224         const bool is_input = (dupe_op->slots_in == dupe_op_slot_args);
225         BMVert *v, *v2, *v3;
226         BMIter iter;
227
228         /* Add the DerivedMesh's elements to the BMesh. The pre-existing
229          * elements were already tagged, so the new elements can be
230          * identified by not having the BM_ELEM_TAG flag set. */
231         DM_to_bmesh_ex(dm, bm, false);
232
233         if (amd->flags & MOD_ARR_MERGE) {
234                 /* if merging is enabled, find doubles */
235                 
236                 BMOIter oiter;
237                 BMOperator find_op;
238                 BMOpSlot *slot_targetmap;
239
240                 BMO_op_initf(bm, &find_op, (BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE),
241                              is_input ?  /* ugh */
242                              "find_doubles verts=%Hv dist=%f keep_verts=%s" :
243                              "find_doubles verts=%Hv dist=%f keep_verts=%S",
244                              BM_ELEM_TAG, amd->merge_dist,
245                              dupe_op, dupe_slot_name);
246
247                 /* append the dupe's geom to the findop input verts */
248                 if (is_input) {
249                         BMO_slot_buffer_append(&find_op, slots_in, "verts",
250                                                dupe_op,  slots_in, dupe_slot_name);
251                 }
252                 else if (dupe_op->slots_out == dupe_op_slot_args) {
253                         BMO_slot_buffer_append(&find_op, slots_in,  "verts",
254                                                dupe_op,  slots_out, dupe_slot_name);
255                 }
256                 else {
257                         BLI_assert(0);
258                 }
259
260                 /* transform and tag verts */
261                 BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
262                         if (!BM_elem_flag_test(v, BM_ELEM_TAG)) {
263                                 mul_m4_v3(mat, v->co);
264                                 BM_elem_flag_enable(v, BM_ELEM_TAG);
265                         }
266                 }
267
268                 BMO_op_exec(bm, &find_op);
269
270                 slot_targetmap = BMO_slot_get(weld_op->slots_in, "targetmap");
271
272                 /* add new merge targets to weld operator */
273                 BMO_ITER (v, &oiter, find_op.slots_out, "targetmap.out", 0) {
274                         v2 = BMO_iter_map_value_ptr(&oiter);
275                         /* check in case the target vertex (v2) is already marked
276                          * for merging */
277                         while ((v3 = BMO_slot_map_elem_get(slot_targetmap, v2))) {
278                                 v2 = v3;
279                         }
280                         BMO_slot_map_elem_insert(weld_op, slot_targetmap, v, v2);
281                 }
282
283                 BMO_op_finish(bm, &find_op);
284         }
285         else {
286                 /* transform and tag verts */
287                 BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
288                         if (!BM_elem_flag_test(v, BM_ELEM_TAG)) {
289                                 mul_m4_v3(mat, v->co);
290                                 BM_elem_flag_enable(v, BM_ELEM_TAG);
291                         }
292                 }
293         }
294 }
295
296 static void merge_first_last(BMesh *bm,
297                              const ArrayModifierData *amd,
298                              BMOperator *dupe_first,
299                              BMOperator *dupe_last,
300                              BMOperator *weld_op)
301 {
302         BMOperator find_op;
303         BMOIter oiter;
304         BMVert *v, *v2;
305         BMOpSlot *slot_targetmap;
306
307         BMO_op_initf(bm, &find_op, (BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE),
308                      "find_doubles verts=%s dist=%f keep_verts=%s",
309                      dupe_first, "geom", amd->merge_dist,
310                      dupe_first, "geom");
311
312         /* append the last dupe's geom to the findop input verts */
313         BMO_slot_buffer_append(&find_op,  slots_in,  "verts",
314                                dupe_last, slots_out, "geom.out");
315
316         BMO_op_exec(bm, &find_op);
317
318         /* add new merge targets to weld operator */
319         slot_targetmap = BMO_slot_get(weld_op->slots_in, "targetmap");
320         BMO_ITER (v, &oiter, find_op.slots_out, "targetmap.out", 0) {
321                 if (!BMO_slot_map_contains(slot_targetmap, v)) {
322                         v2 = BMO_iter_map_value_ptr(&oiter);
323                         BMO_slot_map_elem_insert(weld_op, slot_targetmap, v, v2);
324                 }
325         }
326
327         BMO_op_finish(bm, &find_op);
328 }
329
330 static DerivedMesh *arrayModifier_doArray(ArrayModifierData *amd,
331                                           Scene *scene, Object *ob, DerivedMesh *dm,
332                                           ModifierApplyFlag flag)
333 {
334         DerivedMesh *result;
335         BMesh *bm = DM_to_bmesh(dm, false);
336         BMOperator first_dupe_op, dupe_op, old_dupe_op, weld_op;
337         BMVert **first_geom = NULL;
338         int i, j;
339         int index_len = -1;  /* initialize to an invalid value */
340         /* offset matrix */
341         float offset[4][4];
342         float final_offset[4][4];
343         float length = amd->length;
344         int count = amd->count, maxVerts;
345         int *indexMap = NULL;
346         DerivedMesh *start_cap = NULL, *end_cap = NULL;
347         MVert *src_mvert;
348         BMOpSlot *slot_targetmap = NULL;  /* for weld_op */
349
350         /* need to avoid infinite recursion here */
351         if (amd->start_cap && amd->start_cap != ob && amd->start_cap->type == OB_MESH)
352                 start_cap = get_dm_for_modifier(amd->start_cap, flag);
353         if (amd->end_cap && amd->end_cap != ob && amd->end_cap->type == OB_MESH)
354                 end_cap = get_dm_for_modifier(amd->end_cap, flag);
355
356         unit_m4(offset);
357
358         src_mvert = dm->getVertArray(dm);
359         maxVerts = dm->getNumVerts(dm);
360
361         if (amd->offset_type & MOD_ARR_OFF_CONST)
362                 add_v3_v3v3(offset[3], offset[3], amd->offset);
363         if (amd->offset_type & MOD_ARR_OFF_RELATIVE) {
364                 for (j = 0; j < 3; j++)
365                         offset[3][j] += amd->scale[j] * vertarray_size(src_mvert, maxVerts, j);
366         }
367
368         if ((amd->offset_type & MOD_ARR_OFF_OBJ) && (amd->offset_ob)) {
369                 float obinv[4][4];
370                 float result_mat[4][4];
371
372                 if (ob)
373                         invert_m4_m4(obinv, ob->obmat);
374                 else
375                         unit_m4(obinv);
376
377                 mul_serie_m4(result_mat, offset,
378                              obinv, amd->offset_ob->obmat,
379                              NULL, NULL, NULL, NULL, NULL);
380                 copy_m4_m4(offset, result_mat);
381         }
382
383         if (amd->fit_type == MOD_ARR_FITCURVE && amd->curve_ob) {
384                 Curve *cu = amd->curve_ob->data;
385                 if (cu) {
386 #ifdef CYCLIC_DEPENDENCY_WORKAROUND
387                         if (amd->curve_ob->curve_cache == NULL) {
388                                 BKE_displist_make_curveTypes(scene, amd->curve_ob, false);
389                         }
390 #endif
391
392                         if (amd->curve_ob->curve_cache && amd->curve_ob->curve_cache->path) {
393                                 float scale = mat4_to_scale(amd->curve_ob->obmat);
394                                 length = scale * amd->curve_ob->curve_cache->path->totdist;
395                         }
396                 }
397         }
398
399         /* calculate the maximum number of copies which will fit within the
400          * prescribed length */
401         if (amd->fit_type == MOD_ARR_FITLENGTH || amd->fit_type == MOD_ARR_FITCURVE) {
402                 float dist = len_v3(offset[3]);
403
404                 if (dist > 1e-6f)
405                         /* this gives length = first copy start to last copy end
406                          * add a tiny offset for floating point rounding errors */
407                         count = (length + 1e-6f) / dist;
408                 else
409                         /* if the offset has no translation, just make one copy */
410                         count = 1;
411         }
412
413         if (count < 1)
414                 count = 1;
415
416         /* calculate the offset matrix of the final copy (for merging) */
417         unit_m4(final_offset);
418
419         for (j = 0; j < count - 1; j++) {
420                 float tmp_mat[4][4];
421                 mul_m4_m4m4(tmp_mat, offset, final_offset);
422                 copy_m4_m4(final_offset, tmp_mat);
423         }
424
425         /* BMESH_TODO: bumping up the stack level avoids computing the normals
426          * after every top-level operator execution (and this modifier has the
427          * potential to execute a *lot* of top-level BMOps. There should be a
428          * cleaner way to do this. One possibility: a "mirror" BMOp would
429          * certainly help by compressing it all into one top-level BMOp that
430          * executes a lot of second-level BMOps. */
431         BM_mesh_elem_toolflags_ensure(bm);
432         BMO_push(bm, NULL);
433         bmesh_edit_begin(bm, 0);
434
435         if (amd->flags & MOD_ARR_MERGE) {
436                 BMO_op_init(bm, &weld_op, (BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE),
437                             "weld_verts");
438
439                 slot_targetmap = BMO_slot_get(weld_op.slots_in, "targetmap");
440         }
441
442         BMO_op_initf(bm, &dupe_op, (BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE),
443                      "duplicate geom=%avef");
444         first_dupe_op = dupe_op;
445
446         for (j = 0; j < count - 1; j++) {
447                 BMVert *v, *v2, *v3;
448                 BMOpSlot *geom_slot;
449                 BMOpSlot *geom_out_slot;
450                 BMOIter oiter;
451
452                 if (j != 0) {
453                         BMO_op_initf(bm, &dupe_op,
454                                      (BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE),
455                                      "duplicate geom=%S", &old_dupe_op, "geom.out");
456                 }
457                 BMO_op_exec(bm, &dupe_op);
458
459                 geom_slot   = BMO_slot_get(dupe_op.slots_in,  "geom");
460                 geom_out_slot = BMO_slot_get(dupe_op.slots_out, "geom.out");
461
462                 if ((amd->flags & MOD_ARR_MERGEFINAL) && j == 0) {
463                         int first_geom_bytes = sizeof(BMVert *) * geom_slot->len;
464                                 
465                         /* make a copy of the initial geometry ordering so the
466                          * last duplicate can be merged into it */
467                         first_geom = MEM_mallocN(first_geom_bytes, "first_geom");
468                         memcpy(first_geom, geom_slot->data.buf, first_geom_bytes);
469                 }
470
471                 /* apply transformation matrix */
472                 BMO_ITER (v, &oiter, dupe_op.slots_out, "geom.out", BM_VERT) {
473                         mul_m4_v3(offset, v->co);
474                 }
475
476                 if (amd->flags & MOD_ARR_MERGE) {
477                         /*calculate merge mapping*/
478                         if (j == 0) {
479                                 indexMap = find_doubles_index_map(bm, &dupe_op,
480                                                                   amd, &index_len);
481                         }
482
483 #define _E(s, i) ((BMVert **)(s)->data.buf)[i]
484
485                         /* ensure this is set */
486                         BLI_assert(index_len != -1);
487
488                         for (i = 0; i < index_len; i++) {
489                                 if (!indexMap[i]) continue;
490
491                                 /* merge v (from 'geom.out') into v2 (from old 'geom') */
492                                 v = _E(geom_out_slot, i - geom_slot->len);
493                                 v2 = _E(geom_slot, indexMap[i] - 1);
494
495                                 /* check in case the target vertex (v2) is already marked
496                                  * for merging */
497                                 while ((v3 = BMO_slot_map_elem_get(slot_targetmap, v2))) {
498                                         v2 = v3;
499                                 }
500
501                                 BMO_slot_map_elem_insert(&weld_op, slot_targetmap, v, v2);
502                         }
503
504 #undef _E
505                 }
506
507                 /* already copied earlier, but after executation more slot
508                  * memory may be allocated */
509                 if (j == 0)
510                         first_dupe_op = dupe_op;
511                 
512                 if (j >= 2)
513                         BMO_op_finish(bm, &old_dupe_op);
514                 old_dupe_op = dupe_op;
515         }
516
517         if ((amd->flags & MOD_ARR_MERGE) &&
518             (amd->flags & MOD_ARR_MERGEFINAL) &&
519             (count > 1))
520         {
521                 /* Merge first and last copies. Note that we can't use the
522                  * indexMap for this because (unless the array is forming a
523                  * loop) the offset between first and last is different from
524                  * dupe X to dupe X+1. */
525
526                 merge_first_last(bm, amd, &first_dupe_op, &dupe_op, &weld_op);
527         }
528
529         /* start capping */
530         if (start_cap || end_cap) {
531                 BM_mesh_elem_hflag_enable_all(bm, BM_VERT, BM_ELEM_TAG, false);
532
533                 if (start_cap) {
534                         float startoffset[4][4];
535                         invert_m4_m4(startoffset, offset);
536                         bm_merge_dm_transform(bm, start_cap, startoffset, amd,
537                                               &first_dupe_op, first_dupe_op.slots_in, "geom", &weld_op);
538                 }
539
540                 if (end_cap) {
541                         float endoffset[4][4];
542                         mul_m4_m4m4(endoffset, offset, final_offset);
543                         bm_merge_dm_transform(bm, end_cap, endoffset, amd,
544                                               &dupe_op, (count == 1) ? dupe_op.slots_in : dupe_op.slots_out,
545                                               (count == 1) ? "geom" : "geom.out", &weld_op);
546                 }
547         }
548         /* done capping */
549
550         /* free remaining dupe operators */
551         BMO_op_finish(bm, &first_dupe_op);
552         if (count > 2)
553                 BMO_op_finish(bm, &dupe_op);
554
555         /* run merge operator */
556         if (amd->flags & MOD_ARR_MERGE) {
557                 BMO_op_exec(bm, &weld_op);
558                 BMO_op_finish(bm, &weld_op);
559         }
560
561         /* Bump the stack level back down to match the adjustment up above */
562         BMO_pop(bm);
563
564         result = CDDM_from_bmesh(bm, false);
565
566         if ((dm->dirty & DM_DIRTY_NORMALS) ||
567             ((amd->offset_type & MOD_ARR_OFF_OBJ) && (amd->offset_ob)))
568         {
569                 /* Update normals in case offset object has rotation. */
570                 result->dirty |= DM_DIRTY_NORMALS;
571         }
572
573         BM_mesh_free(bm);
574
575         if (indexMap)
576                 MEM_freeN(indexMap);
577         if (first_geom)
578                 MEM_freeN(first_geom);
579
580         return result;
581 }
582
583 static DerivedMesh *applyModifier(ModifierData *md, Object *ob,
584                                   DerivedMesh *dm,
585                                   ModifierApplyFlag flag)
586 {
587         DerivedMesh *result;
588         ArrayModifierData *amd = (ArrayModifierData *) md;
589
590         result = arrayModifier_doArray(amd, md->scene, ob, dm, flag);
591
592         return result;
593 }
594
595
596 ModifierTypeInfo modifierType_Array = {
597         /* name */              "Array",
598         /* structName */        "ArrayModifierData",
599         /* structSize */        sizeof(ArrayModifierData),
600         /* type */              eModifierTypeType_Constructive,
601         /* flags */             eModifierTypeFlag_AcceptsMesh |
602                                 eModifierTypeFlag_SupportsMapping |
603                                 eModifierTypeFlag_SupportsEditmode |
604                                 eModifierTypeFlag_EnableInEditmode |
605                                 eModifierTypeFlag_AcceptsCVs,
606
607         /* copyData */          copyData,
608         /* deformVerts */       NULL,
609         /* deformMatrices */    NULL,
610         /* deformVertsEM */     NULL,
611         /* deformMatricesEM */  NULL,
612         /* applyModifier */     applyModifier,
613         /* applyModifierEM */   NULL,
614         /* initData */          initData,
615         /* requiredDataMask */  NULL,
616         /* freeData */          NULL,
617         /* isDisabled */        NULL,
618         /* updateDepgraph */    updateDepgraph,
619         /* dependsOnTime */     NULL,
620         /* dependsOnNormals */  NULL,
621         /* foreachObjectLink */ foreachObjectLink,
622         /* foreachIDLink */     NULL,
623         /* foreachTexLink */    NULL,
624 };