Cleanup: blank lines over doxy headers
[blender.git] / source / blender / blenkernel / intern / deform.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) 2001-2002 by NaN Holding BV.
19  * All rights reserved.
20  *
21  * The Original Code is: all of this file.
22  *
23  * Contributor(s): Reevan McKay
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27
28 /** \file blender/blenkernel/intern/deform.c
29  *  \ingroup bke
30  */
31
32
33 #include <string.h>
34 #include <math.h>
35 #include <ctype.h>
36 #include <stdlib.h>
37 #include <stddef.h>
38
39 #include "MEM_guardedalloc.h"
40
41 #include "DNA_meshdata_types.h"
42 #include "DNA_mesh_types.h"
43 #include "DNA_object_types.h"
44 #include "DNA_scene_types.h"
45
46 #include "BLI_listbase.h"
47 #include "BLI_math.h"
48 #include "BLI_string.h"
49 #include "BLI_string_utils.h"
50 #include "BLI_utildefines.h"
51
52 #include "BLT_translation.h"
53
54 #include "BKE_customdata.h"
55 #include "BKE_data_transfer.h"
56 #include "BKE_deform.h"  /* own include */
57 #include "BKE_mesh.h"
58 #include "BKE_mesh_mapping.h"
59 #include "BKE_object.h"
60 #include "BKE_object_deform.h"
61
62 #include "data_transfer_intern.h"
63
64
65 bDeformGroup *BKE_defgroup_new(Object *ob, const char *name)
66 {
67         bDeformGroup *defgroup;
68
69         BLI_assert(OB_TYPE_SUPPORT_VGROUP(ob->type));
70
71         defgroup = MEM_callocN(sizeof(bDeformGroup), __func__);
72
73         BLI_strncpy(defgroup->name, name, sizeof(defgroup->name));
74
75         BLI_addtail(&ob->defbase, defgroup);
76         defgroup_unique_name(defgroup, ob);
77
78         BKE_object_batch_cache_dirty_tag(ob);
79
80         return defgroup;
81 }
82
83 void defgroup_copy_list(ListBase *outbase, const ListBase *inbase)
84 {
85         bDeformGroup *defgroup, *defgroupn;
86
87         BLI_listbase_clear(outbase);
88
89         for (defgroup = inbase->first; defgroup; defgroup = defgroup->next) {
90                 defgroupn = defgroup_duplicate(defgroup);
91                 BLI_addtail(outbase, defgroupn);
92         }
93 }
94
95 bDeformGroup *defgroup_duplicate(const bDeformGroup *ingroup)
96 {
97         bDeformGroup *outgroup;
98
99         if (!ingroup) {
100                 BLI_assert(0);
101                 return NULL;
102         }
103
104         outgroup = MEM_callocN(sizeof(bDeformGroup), "copy deformGroup");
105
106         /* For now, just copy everything over. */
107         memcpy(outgroup, ingroup, sizeof(bDeformGroup));
108
109         outgroup->next = outgroup->prev = NULL;
110
111         return outgroup;
112 }
113
114 /**
115  * Overwrite weights filtered by vgroup_subset.
116  * - do nothing if neither are set.
117  * - add destination weight if needed
118  */
119 void defvert_copy_subset(
120         MDeformVert *dvert_dst, const MDeformVert *dvert_src,
121         const bool *vgroup_subset, const int vgroup_tot)
122 {
123         int defgroup;
124         for (defgroup = 0; defgroup < vgroup_tot; defgroup++) {
125                 if (vgroup_subset[defgroup]) {
126                         defvert_copy_index(dvert_dst, defgroup, dvert_src, defgroup);
127                 }
128         }
129 }
130
131 /**
132  * Overwrite weights filtered by vgroup_subset and with mirroring specified by the flip map
133  * - do nothing if neither are set.
134  * - add destination weight if needed
135  */
136 void defvert_mirror_subset(
137         MDeformVert *dvert_dst, const MDeformVert *dvert_src,
138         const bool *vgroup_subset, const int vgroup_tot,
139         const int *flip_map, const int flip_map_len)
140 {
141         int defgroup;
142         for (defgroup = 0; defgroup < vgroup_tot && defgroup < flip_map_len; defgroup++) {
143                 if (vgroup_subset[defgroup] && (dvert_dst != dvert_src || flip_map[defgroup] != defgroup)) {
144                         defvert_copy_index(dvert_dst, flip_map[defgroup], dvert_src, defgroup);
145                 }
146         }
147 }
148
149 void defvert_copy(MDeformVert *dvert_dst, const MDeformVert *dvert_src)
150 {
151         if (dvert_dst->totweight == dvert_src->totweight) {
152                 if (dvert_src->totweight)
153                         memcpy(dvert_dst->dw, dvert_src->dw, dvert_src->totweight * sizeof(MDeformWeight));
154         }
155         else {
156                 if (dvert_dst->dw)
157                         MEM_freeN(dvert_dst->dw);
158
159                 if (dvert_src->totweight)
160                         dvert_dst->dw = MEM_dupallocN(dvert_src->dw);
161                 else
162                         dvert_dst->dw = NULL;
163
164                 dvert_dst->totweight = dvert_src->totweight;
165         }
166 }
167
168 /**
169  * Copy an index from one dvert to another.
170  * - do nothing if neither are set.
171  * - add destination weight if needed.
172  */
173 void defvert_copy_index(
174         MDeformVert       *dvert_dst, const int defgroup_dst,
175         const MDeformVert *dvert_src, const int defgroup_src)
176 {
177         MDeformWeight *dw_src, *dw_dst;
178
179         dw_src = defvert_find_index(dvert_src, defgroup_src);
180
181         if (dw_src) {
182                 /* source is valid, verify destination */
183                 dw_dst = defvert_verify_index(dvert_dst, defgroup_dst);
184                 dw_dst->weight = dw_src->weight;
185         }
186         else {
187                 /* source was NULL, assign zero, could also remove */
188                 dw_dst = defvert_find_index(dvert_dst, defgroup_dst);
189
190                 if (dw_dst) {
191                         dw_dst->weight = 0.0f;
192                 }
193         }
194 }
195
196 /**
197  * Only sync over matching weights, don't add or remove groups
198  * warning, loop within loop.
199  */
200 void defvert_sync(MDeformVert *dvert_dst, const MDeformVert *dvert_src, const bool use_verify)
201 {
202         if (dvert_src->totweight && dvert_dst->totweight) {
203                 int i;
204                 MDeformWeight *dw_src;
205                 for (i = 0, dw_src = dvert_src->dw; i < dvert_src->totweight; i++, dw_src++) {
206                         MDeformWeight *dw_dst;
207                         if (use_verify) dw_dst = defvert_verify_index(dvert_dst, dw_src->def_nr);
208                         else            dw_dst = defvert_find_index(dvert_dst, dw_src->def_nr);
209
210                         if (dw_dst) {
211                                 dw_dst->weight = dw_src->weight;
212                         }
213                 }
214         }
215 }
216
217 /**
218  * be sure all flip_map values are valid
219  */
220 void defvert_sync_mapped(
221         MDeformVert *dvert_dst, const MDeformVert *dvert_src,
222         const int *flip_map, const int flip_map_len, const bool use_verify)
223 {
224         if (dvert_src->totweight && dvert_dst->totweight) {
225                 int i;
226                 MDeformWeight *dw_src;
227                 for (i = 0, dw_src = dvert_src->dw; i < dvert_src->totweight; i++, dw_src++) {
228                         if (dw_src->def_nr < flip_map_len) {
229                                 MDeformWeight *dw_dst;
230                                 if (use_verify) dw_dst = defvert_verify_index(dvert_dst, flip_map[dw_src->def_nr]);
231                                 else            dw_dst = defvert_find_index(dvert_dst, flip_map[dw_src->def_nr]);
232
233                                 if (dw_dst) {
234                                         dw_dst->weight = dw_src->weight;
235                                 }
236                         }
237                 }
238         }
239 }
240
241 /**
242  * be sure all flip_map values are valid
243  */
244 void defvert_remap(MDeformVert *dvert, int *map, const int map_len)
245 {
246         MDeformWeight *dw = dvert->dw;
247         unsigned int i;
248         for (i = dvert->totweight; i != 0; i--, dw++) {
249                 if (dw->def_nr < map_len) {
250                         dw->def_nr = map[dw->def_nr];
251
252                         /* just in case */
253                         BLI_assert(dw->def_nr >= 0);
254                 }
255         }
256 }
257
258 /**
259  * Same as #defvert_normalize but takes a bool array.
260  */
261 void defvert_normalize_subset(MDeformVert *dvert,
262                               const bool *vgroup_subset, const int vgroup_tot)
263 {
264         if (dvert->totweight == 0) {
265                 /* nothing */
266         }
267         else if (dvert->totweight == 1) {
268                 MDeformWeight *dw = dvert->dw;
269                 if ((dw->def_nr < vgroup_tot) && vgroup_subset[dw->def_nr]) {
270                         dw->weight = 1.0f;
271                 }
272         }
273         else {
274                 MDeformWeight *dw;
275                 unsigned int i;
276                 float tot_weight = 0.0f;
277
278                 for (i = dvert->totweight, dw = dvert->dw; i != 0; i--, dw++) {
279                         if ((dw->def_nr < vgroup_tot) && vgroup_subset[dw->def_nr]) {
280                                 tot_weight += dw->weight;
281                         }
282                 }
283
284                 if (tot_weight > 0.0f) {
285                         float scalar = 1.0f / tot_weight;
286                         for (i = dvert->totweight, dw = dvert->dw; i != 0; i--, dw++) {
287                                 if ((dw->def_nr < vgroup_tot) && vgroup_subset[dw->def_nr]) {
288                                         dw->weight *= scalar;
289
290                                         /* in case of division errors with very low weights */
291                                         CLAMP(dw->weight, 0.0f, 1.0f);
292                                 }
293                         }
294                 }
295         }
296 }
297
298 void defvert_normalize(MDeformVert *dvert)
299 {
300         if (dvert->totweight == 0) {
301                 /* nothing */
302         }
303         else if (dvert->totweight == 1) {
304                 dvert->dw[0].weight = 1.0f;
305         }
306         else {
307                 MDeformWeight *dw;
308                 unsigned int i;
309                 float tot_weight = 0.0f;
310
311                 for (i = dvert->totweight, dw = dvert->dw; i != 0; i--, dw++) {
312                         tot_weight += dw->weight;
313                 }
314
315                 if (tot_weight > 0.0f) {
316                         float scalar = 1.0f / tot_weight;
317                         for (i = dvert->totweight, dw = dvert->dw; i != 0; i--, dw++) {
318                                 dw->weight *= scalar;
319
320                                 /* in case of division errors with very low weights */
321                                 CLAMP(dw->weight, 0.0f, 1.0f);
322                         }
323                 }
324         }
325 }
326
327 /**
328  * Same as defvert_normalize() if the locked vgroup is not a member of the subset
329  */
330 void defvert_normalize_lock_single(
331         MDeformVert *dvert,
332         const bool *vgroup_subset, const int vgroup_tot,
333         const int def_nr_lock)
334 {
335         if (dvert->totweight == 0) {
336                 /* nothing */
337         }
338         else if (dvert->totweight == 1) {
339                 MDeformWeight *dw = dvert->dw;
340                 if ((dw->def_nr < vgroup_tot) && vgroup_subset[dw->def_nr]) {
341                         if (def_nr_lock != 0) {
342                                 dw->weight = 1.0f;
343                         }
344                 }
345         }
346         else {
347                 MDeformWeight *dw_lock = NULL;
348                 MDeformWeight *dw;
349                 unsigned int i;
350                 float tot_weight = 0.0f;
351                 float lock_iweight = 1.0f;
352
353                 for (i = dvert->totweight, dw = dvert->dw; i != 0; i--, dw++) {
354                         if ((dw->def_nr < vgroup_tot) && vgroup_subset[dw->def_nr]) {
355                                 if (dw->def_nr != def_nr_lock) {
356                                         tot_weight += dw->weight;
357                                 }
358                                 else {
359                                         dw_lock = dw;
360                                         lock_iweight = (1.0f - dw_lock->weight);
361                                         CLAMP(lock_iweight, 0.0f, 1.0f);
362                                 }
363                         }
364                 }
365
366                 if (tot_weight > 0.0f) {
367                         /* paranoid, should be 1.0 but in case of float error clamp anyway */
368
369                         float scalar = (1.0f / tot_weight) * lock_iweight;
370                         for (i = dvert->totweight, dw = dvert->dw; i != 0; i--, dw++) {
371                                 if ((dw->def_nr < vgroup_tot) && vgroup_subset[dw->def_nr]) {
372                                         if (dw != dw_lock) {
373                                                 dw->weight *= scalar;
374
375                                                 /* in case of division errors with very low weights */
376                                                 CLAMP(dw->weight, 0.0f, 1.0f);
377                                         }
378                                 }
379                         }
380                 }
381         }
382 }
383
384 /**
385  * Same as defvert_normalize() if no locked vgroup is a member of the subset
386  */
387 void defvert_normalize_lock_map(
388         MDeformVert *dvert,
389         const bool *vgroup_subset, const int vgroup_tot,
390         const bool *lock_flags, const int defbase_tot)
391 {
392         if (dvert->totweight == 0) {
393                 /* nothing */
394         }
395         else if (dvert->totweight == 1) {
396                 MDeformWeight *dw = dvert->dw;
397                 if ((dw->def_nr < vgroup_tot) && vgroup_subset[dw->def_nr]) {
398                         if ((dw->def_nr < defbase_tot) && (lock_flags[dw->def_nr] == false)) {
399                                 dw->weight = 1.0f;
400                         }
401                 }
402         }
403         else {
404                 MDeformWeight *dw;
405                 unsigned int i;
406                 float tot_weight = 0.0f;
407                 float lock_iweight = 0.0f;
408
409                 for (i = dvert->totweight, dw = dvert->dw; i != 0; i--, dw++) {
410                         if ((dw->def_nr < vgroup_tot) && vgroup_subset[dw->def_nr]) {
411                                 if ((dw->def_nr < defbase_tot) && (lock_flags[dw->def_nr] == false)) {
412                                         tot_weight += dw->weight;
413                                 }
414                                 else {
415                                         /* invert after */
416                                         lock_iweight += dw->weight;
417                                 }
418                         }
419                 }
420
421                 lock_iweight = max_ff(0.0f, 1.0f - lock_iweight);
422
423                 if (tot_weight > 0.0f) {
424                         /* paranoid, should be 1.0 but in case of float error clamp anyway */
425
426                         float scalar = (1.0f / tot_weight) * lock_iweight;
427                         for (i = dvert->totweight, dw = dvert->dw; i != 0; i--, dw++) {
428                                 if ((dw->def_nr < vgroup_tot) && vgroup_subset[dw->def_nr]) {
429                                         if ((dw->def_nr < defbase_tot) && (lock_flags[dw->def_nr] == false)) {
430                                                 dw->weight *= scalar;
431
432                                                 /* in case of division errors with very low weights */
433                                                 CLAMP(dw->weight, 0.0f, 1.0f);
434                                         }
435                                 }
436                         }
437                 }
438         }
439 }
440
441 void defvert_flip(MDeformVert *dvert, const int *flip_map, const int flip_map_len)
442 {
443         MDeformWeight *dw;
444         int i;
445
446         for (dw = dvert->dw, i = 0; i < dvert->totweight; dw++, i++) {
447                 if (dw->def_nr < flip_map_len) {
448                         if (flip_map[dw->def_nr] >= 0) {
449                                 dw->def_nr = flip_map[dw->def_nr];
450                         }
451                 }
452         }
453 }
454
455 void defvert_flip_merged(MDeformVert *dvert, const int *flip_map, const int flip_map_len)
456 {
457         MDeformWeight *dw, *dw_cpy;
458         float weight;
459         int i, totweight = dvert->totweight;
460
461         /* copy weights */
462         for (dw = dvert->dw, i = 0; i < totweight; dw++, i++) {
463                 if (dw->def_nr < flip_map_len) {
464                         if (flip_map[dw->def_nr] >= 0) {
465                                 /* error checkers complain of this but we'll never get NULL return */
466                                 dw_cpy = defvert_verify_index(dvert, flip_map[dw->def_nr]);
467                                 dw = &dvert->dw[i]; /* in case array got realloced */
468
469                                 /* distribute weights: if only one of the vertex groups was
470                                  * assigned this will halve the weights, otherwise it gets
471                                  * evened out. this keeps it proportional to other groups */
472                                 weight = 0.5f * (dw_cpy->weight + dw->weight);
473                                 dw_cpy->weight = weight;
474                                 dw->weight = weight;
475                         }
476                 }
477         }
478 }
479
480 bDeformGroup *defgroup_find_name(Object *ob, const char *name)
481 {
482         return (name && name[0] != '\0') ? BLI_findstring(&ob->defbase, name, offsetof(bDeformGroup, name)) : NULL;
483 }
484
485 int defgroup_name_index(Object *ob, const char *name)
486 {
487         return (name && name[0] != '\0') ? BLI_findstringindex(&ob->defbase, name, offsetof(bDeformGroup, name)) : -1;
488 }
489
490 /**
491  * \note caller must free.
492  */
493 int *defgroup_flip_map(Object *ob, int *flip_map_len, const bool use_default)
494 {
495         int defbase_tot = *flip_map_len = BLI_listbase_count(&ob->defbase);
496
497         if (defbase_tot == 0) {
498                 return NULL;
499         }
500         else {
501                 bDeformGroup *dg;
502                 char name_flip[sizeof(dg->name)];
503                 int i, flip_num, *map = MEM_mallocN(defbase_tot * sizeof(int), __func__);
504
505                 for (i = 0; i < defbase_tot; i++) {
506                         map[i] = -1;
507                 }
508
509                 for (dg = ob->defbase.first, i = 0; dg; dg = dg->next, i++) {
510                         if (map[i] == -1) { /* may be calculated previously */
511
512                                 /* in case no valid value is found, use this */
513                                 if (use_default)
514                                         map[i] = i;
515
516                                 BLI_string_flip_side_name(name_flip, dg->name, false, sizeof(name_flip));
517
518                                 if (!STREQ(name_flip, dg->name)) {
519                                         flip_num = defgroup_name_index(ob, name_flip);
520                                         if (flip_num >= 0) {
521                                                 map[i] = flip_num;
522                                                 map[flip_num] = i; /* save an extra lookup */
523                                         }
524                                 }
525                         }
526                 }
527                 return map;
528         }
529 }
530
531 /**
532  * \note caller must free.
533  */
534 int *defgroup_flip_map_single(Object *ob, int *flip_map_len, const bool use_default, int defgroup)
535 {
536         int defbase_tot = *flip_map_len = BLI_listbase_count(&ob->defbase);
537
538         if (defbase_tot == 0) {
539                 return NULL;
540         }
541         else {
542                 bDeformGroup *dg;
543                 char name_flip[sizeof(dg->name)];
544                 int i, flip_num, *map = MEM_mallocN(defbase_tot * sizeof(int), __func__);
545
546                 for (i = 0; i < defbase_tot; i++) {
547                         map[i] = use_default ? i : -1;
548                 }
549
550                 dg = BLI_findlink(&ob->defbase, defgroup);
551
552                 BLI_string_flip_side_name(name_flip, dg->name, false, sizeof(name_flip));
553                 if (!STREQ(name_flip, dg->name)) {
554                         flip_num = defgroup_name_index(ob, name_flip);
555
556                         if (flip_num != -1) {
557                                 map[defgroup] = flip_num;
558                                 map[flip_num] = defgroup;
559                         }
560                 }
561
562                 return map;
563         }
564 }
565
566 int defgroup_flip_index(Object *ob, int index, const bool use_default)
567 {
568         bDeformGroup *dg = BLI_findlink(&ob->defbase, index);
569         int flip_index = -1;
570
571         if (dg) {
572                 char name_flip[sizeof(dg->name)];
573                 BLI_string_flip_side_name(name_flip, dg->name, false, sizeof(name_flip));
574
575                 if (!STREQ(name_flip, dg->name)) {
576                         flip_index = defgroup_name_index(ob, name_flip);
577                 }
578         }
579
580         return (flip_index == -1 && use_default) ? index : flip_index;
581 }
582
583 static bool defgroup_find_name_dupe(const char *name, bDeformGroup *dg, Object *ob)
584 {
585         bDeformGroup *curdef;
586
587         for (curdef = ob->defbase.first; curdef; curdef = curdef->next) {
588                 if (dg != curdef) {
589                         if (STREQ(curdef->name, name)) {
590                                 return true;
591                         }
592                 }
593         }
594
595         return false;
596 }
597
598 static bool defgroup_unique_check(void *arg, const char *name)
599 {
600         struct {Object *ob; void *dg; } *data = arg;
601         return defgroup_find_name_dupe(name, data->dg, data->ob);
602 }
603
604 void defgroup_unique_name(bDeformGroup *dg, Object *ob)
605 {
606         struct {Object *ob; void *dg; } data;
607         data.ob = ob;
608         data.dg = dg;
609
610         BLI_uniquename_cb(defgroup_unique_check, &data, DATA_("Group"), '.', dg->name, sizeof(dg->name));
611 }
612
613 float defvert_find_weight(const struct MDeformVert *dvert, const int defgroup)
614 {
615         MDeformWeight *dw = defvert_find_index(dvert, defgroup);
616         return dw ? dw->weight : 0.0f;
617 }
618
619 /**
620  * Take care with this the rationale is:
621  * - if the object has no vertex group. act like vertex group isn't set and return 1.0,
622  * - if the vertex group exists but the 'defgroup' isn't found on this vertex, _still_ return 0.0
623  *
624  * This is a bit confusing, just saves some checks from the caller.
625  */
626 float defvert_array_find_weight_safe(const struct MDeformVert *dvert, const int index, const int defgroup)
627 {
628         /* Invalid defgroup index means the vgroup selected is invalid, does not exist, in that case it is OK to return 1.0
629          * (i.e. maximum weight, as if no vgroup was selected).
630          * But in case of valid defgroup and NULL dvert data pointer, it means that vgroup **is** valid,
631          * and just totally empty, so we shall return '0.0' value then!
632          */
633         if (defgroup == -1) {
634                 return 1.0f;
635         }
636         else if (dvert == NULL) {
637                 return 0.0f;
638         }
639
640         return defvert_find_weight(dvert + index, defgroup);
641 }
642
643
644 MDeformWeight *defvert_find_index(const MDeformVert *dvert, const int defgroup)
645 {
646         if (dvert && defgroup >= 0) {
647                 MDeformWeight *dw = dvert->dw;
648                 unsigned int i;
649
650                 for (i = dvert->totweight; i != 0; i--, dw++) {
651                         if (dw->def_nr == defgroup) {
652                                 return dw;
653                         }
654                 }
655         }
656         else {
657                 BLI_assert(0);
658         }
659
660         return NULL;
661 }
662
663 /**
664  * Ensures that mv has a deform weight entry for the specified defweight group.
665  *
666  * \note this function is mirrored in editmesh_tools.c, for use for editvertices.
667  */
668 MDeformWeight *defvert_verify_index(MDeformVert *dvert, const int defgroup)
669 {
670         MDeformWeight *dw_new;
671
672         /* do this check always, this function is used to check for it */
673         if (!dvert || defgroup < 0) {
674                 BLI_assert(0);
675                 return NULL;
676         }
677
678         dw_new = defvert_find_index(dvert, defgroup);
679         if (dw_new)
680                 return dw_new;
681
682         dw_new = MEM_mallocN(sizeof(MDeformWeight) * (dvert->totweight + 1), "deformWeight");
683         if (dvert->dw) {
684                 memcpy(dw_new, dvert->dw, sizeof(MDeformWeight) * dvert->totweight);
685                 MEM_freeN(dvert->dw);
686         }
687         dvert->dw = dw_new;
688         dw_new += dvert->totweight;
689         dw_new->weight = 0.0f;
690         dw_new->def_nr = defgroup;
691         /* Group index */
692
693         dvert->totweight++;
694
695         return dw_new;
696 }
697
698 /* TODO. merge with code above! */
699
700 /**
701  * Adds the given vertex to the specified vertex group, with given weight.
702  *
703  * \warning this does NOT check for existing, assume caller already knows its not there.
704  */
705 void defvert_add_index_notest(MDeformVert *dvert, int defgroup, const float weight)
706 {
707         MDeformWeight *dw_new;
708
709         /* do this check always, this function is used to check for it */
710         if (!dvert || defgroup < 0) {
711                 BLI_assert(0);
712                 return;
713         }
714
715         dw_new = MEM_callocN(sizeof(MDeformWeight) * (dvert->totweight + 1), "defvert_add_to group, new deformWeight");
716         if (dvert->dw) {
717                 memcpy(dw_new, dvert->dw, sizeof(MDeformWeight) * dvert->totweight);
718                 MEM_freeN(dvert->dw);
719         }
720         dvert->dw = dw_new;
721         dw_new += dvert->totweight;
722         dw_new->weight = weight;
723         dw_new->def_nr = defgroup;
724         dvert->totweight++;
725 }
726
727
728 /**
729  * Removes the given vertex from the vertex group.
730  *
731  * \warning This function frees the given MDeformWeight, do not use it afterward!
732  */
733 void defvert_remove_group(MDeformVert *dvert, MDeformWeight *dw)
734 {
735         if (dvert && dw) {
736                 int i = dw - dvert->dw;
737
738                 /* Security check! */
739                 if (i < 0 || i >= dvert->totweight) {
740                         return;
741                 }
742
743                 dvert->totweight--;
744                 /* If there are still other deform weights attached to this vert then remove
745                  * this deform weight, and reshuffle the others.
746                  */
747                 if (dvert->totweight) {
748                         BLI_assert(dvert->dw != NULL);
749
750                         if (i != dvert->totweight) {
751                                 dvert->dw[i] = dvert->dw[dvert->totweight];
752                         }
753
754                         dvert->dw = MEM_reallocN(dvert->dw, sizeof(MDeformWeight) * dvert->totweight);
755                 }
756                 else {
757                         /* If there are no other deform weights left then just remove this one. */
758                         MEM_freeN(dvert->dw);
759                         dvert->dw = NULL;
760                 }
761         }
762 }
763
764 void defvert_clear(MDeformVert *dvert)
765 {
766         if (dvert->dw) {
767                 MEM_freeN(dvert->dw);
768                 dvert->dw = NULL;
769         }
770
771         dvert->totweight = 0;
772 }
773
774 /**
775  * \return The first group index shared by both deform verts
776  * or -1 if none are found.
777  */
778 int defvert_find_shared(const MDeformVert *dvert_a, const MDeformVert *dvert_b)
779 {
780         if (dvert_a->totweight && dvert_b->totweight) {
781                 MDeformWeight *dw = dvert_a->dw;
782                 unsigned int i;
783
784                 for (i = dvert_a->totweight; i != 0; i--, dw++) {
785                         if (dw->weight > 0.0f && defvert_find_weight(dvert_b, dw->def_nr) > 0.0f) {
786                                 return dw->def_nr;
787                         }
788                 }
789         }
790
791         return -1;
792 }
793
794 /**
795  * return true if has no weights
796  */
797 bool defvert_is_weight_zero(const struct MDeformVert *dvert, const int defgroup_tot)
798 {
799         MDeformWeight *dw = dvert->dw;
800         unsigned int i;
801         for (i = dvert->totweight; i != 0; i--, dw++) {
802                 if (dw->weight != 0.0f) {
803                         /* check the group is in-range, happens on rare situations */
804                         if (LIKELY(dw->def_nr < defgroup_tot)) {
805                                 return false;
806                         }
807                 }
808         }
809         return true;
810 }
811
812
813 /**
814  * \return The representative weight of a multipaint group, used for
815  * viewport colors and actual painting.
816  *
817  * Result equal to sum of weights with auto normalize, and average otherwise.
818  * Value is not clamped, since painting relies on multiplication being always
819  * commutative with the collective weight function.
820  */
821 float BKE_defvert_multipaint_collective_weight(
822         const struct MDeformVert *dv, int defbase_tot,
823         const bool *defbase_sel, int defbase_tot_sel, bool do_autonormalize)
824 {
825         int i;
826         float total = 0.0f;
827         const MDeformWeight *dw = dv->dw;
828
829         for (i = dv->totweight; i != 0; i--, dw++) {
830                 /* in multipaint, get the average if auto normalize is inactive
831                  * get the sum if it is active */
832                 if (dw->def_nr < defbase_tot) {
833                         if (defbase_sel[dw->def_nr]) {
834                                 total += dw->weight;
835                         }
836                 }
837         }
838
839         if (do_autonormalize == false) {
840                 total /= defbase_tot_sel;
841         }
842
843         return total;
844 }
845
846
847 /* -------------------------------------------------------------------- */
848 /** \name Defvert Array functions
849  * \{ */
850
851 void BKE_defvert_array_copy(MDeformVert *dst, const MDeformVert *src, int copycount)
852 {
853         /* Assumes dst is already set up */
854         int i;
855
856         if (!src || !dst)
857                 return;
858
859         memcpy(dst, src, copycount * sizeof(MDeformVert));
860
861         for (i = 0; i < copycount; i++) {
862                 if (src[i].dw) {
863                         dst[i].dw = MEM_mallocN(sizeof(MDeformWeight) * src[i].totweight, "copy_deformWeight");
864                         memcpy(dst[i].dw, src[i].dw, sizeof(MDeformWeight) * src[i].totweight);
865                 }
866         }
867
868 }
869
870 void BKE_defvert_array_free_elems(MDeformVert *dvert, int totvert)
871 {
872         /* Instead of freeing the verts directly,
873          * call this function to delete any special
874          * vert data */
875         int i;
876
877         if (!dvert)
878                 return;
879
880         /* Free any special data from the verts */
881         for (i = 0; i < totvert; i++) {
882                 if (dvert[i].dw) MEM_freeN(dvert[i].dw);
883         }
884 }
885
886 void BKE_defvert_array_free(MDeformVert *dvert, int totvert)
887 {
888         /* Instead of freeing the verts directly,
889          * call this function to delete any special
890          * vert data */
891         if (!dvert)
892                 return;
893
894         /* Free any special data from the verts */
895         BKE_defvert_array_free_elems(dvert, totvert);
896
897         MEM_freeN(dvert);
898 }
899
900 void BKE_defvert_extract_vgroup_to_vertweights(
901         MDeformVert *dvert, const int defgroup, const int num_verts, float *r_weights, const bool invert_vgroup)
902 {
903         if (dvert && defgroup != -1) {
904                 int i = num_verts;
905
906                 while (i--) {
907                         const float w = defvert_find_weight(&dvert[i], defgroup);
908                         r_weights[i] = invert_vgroup ? (1.0f - w) : w;
909                 }
910         }
911         else {
912                 copy_vn_fl(r_weights, num_verts, invert_vgroup ? 1.0f : 0.0f);
913         }
914 }
915
916 /**
917  * The following three make basic interpolation,
918  * using temp vert_weights array to avoid looking up same weight several times.
919  */
920 void BKE_defvert_extract_vgroup_to_edgeweights(
921         MDeformVert *dvert, const int defgroup, const int num_verts, MEdge *edges, const int num_edges,
922         float *r_weights, const bool invert_vgroup)
923 {
924         if (dvert && defgroup != -1) {
925                 int i = num_edges;
926                 float *tmp_weights = MEM_mallocN(sizeof(*tmp_weights) * (size_t)num_verts, __func__);
927
928                 BKE_defvert_extract_vgroup_to_vertweights(dvert, defgroup, num_verts, tmp_weights, invert_vgroup);
929
930                 while (i--) {
931                         MEdge *me = &edges[i];
932
933                         r_weights[i] = (tmp_weights[me->v1] + tmp_weights[me->v2]) * 0.5f;
934                 }
935
936                 MEM_freeN(tmp_weights);
937         }
938         else {
939                 copy_vn_fl(r_weights, num_edges, 0.0f);
940         }
941 }
942
943 void BKE_defvert_extract_vgroup_to_loopweights(
944         MDeformVert *dvert, const int defgroup, const int num_verts, MLoop *loops, const int num_loops,
945         float *r_weights, const bool invert_vgroup)
946 {
947         if (dvert && defgroup != -1) {
948                 int i = num_loops;
949                 float *tmp_weights = MEM_mallocN(sizeof(*tmp_weights) * (size_t)num_verts, __func__);
950
951                 BKE_defvert_extract_vgroup_to_vertweights(dvert, defgroup, num_verts, tmp_weights, invert_vgroup);
952
953                 while (i--) {
954                         MLoop *ml = &loops[i];
955
956                         r_weights[i] = tmp_weights[ml->v];
957                 }
958
959                 MEM_freeN(tmp_weights);
960         }
961         else {
962                 copy_vn_fl(r_weights, num_loops, 0.0f);
963         }
964 }
965
966 void BKE_defvert_extract_vgroup_to_polyweights(
967         MDeformVert *dvert, const int defgroup, const int num_verts, MLoop *loops, const int UNUSED(num_loops),
968         MPoly *polys, const int num_polys, float *r_weights, const bool invert_vgroup)
969 {
970         if (dvert && defgroup != -1) {
971                 int i = num_polys;
972                 float *tmp_weights = MEM_mallocN(sizeof(*tmp_weights) * (size_t)num_verts, __func__);
973
974                 BKE_defvert_extract_vgroup_to_vertweights(dvert, defgroup, num_verts, tmp_weights, invert_vgroup);
975
976                 while (i--) {
977                         MPoly *mp = &polys[i];
978                         MLoop *ml = &loops[mp->loopstart];
979                         int j = mp->totloop;
980                         float w = 0.0f;
981
982                         for (; j--; ml++) {
983                                 w += tmp_weights[ml->v];
984                         }
985                         r_weights[i] = w / (float)mp->totloop;
986                 }
987
988                 MEM_freeN(tmp_weights);
989         }
990         else {
991                 copy_vn_fl(r_weights, num_polys, 0.0f);
992         }
993 }
994
995 /** \} */
996
997
998 /* -------------------------------------------------------------------- */
999 /** \name Data Transfer
1000  * \{ */
1001
1002 static void vgroups_datatransfer_interp(
1003         const CustomDataTransferLayerMap *laymap, void *dest,
1004         const void **sources, const float *weights, const int count, const float mix_factor)
1005 {
1006         MDeformVert **data_src = (MDeformVert **)sources;
1007         MDeformVert *data_dst = (MDeformVert *)dest;
1008         const int idx_src = laymap->data_src_n;
1009         const int idx_dst = laymap->data_dst_n;
1010
1011         const int mix_mode = laymap->mix_mode;
1012
1013         int i, j;
1014
1015         MDeformWeight *dw_src;
1016         MDeformWeight *dw_dst = defvert_find_index(data_dst, idx_dst);
1017         float weight_src = 0.0f, weight_dst = 0.0f;
1018
1019         if (sources) {
1020                 for (i = count; i--;) {
1021                         for (j = data_src[i]->totweight; j--;) {
1022                                 if ((dw_src = &data_src[i]->dw[j])->def_nr == idx_src) {
1023                                         weight_src += dw_src->weight * weights[i];
1024                                         break;
1025                                 }
1026                         }
1027                 }
1028         }
1029
1030         if (dw_dst) {
1031                 weight_dst = dw_dst->weight;
1032         }
1033         else if (mix_mode == CDT_MIX_REPLACE_ABOVE_THRESHOLD) {
1034                 return;  /* Do not affect destination. */
1035         }
1036
1037         weight_src = data_transfer_interp_float_do(mix_mode, weight_dst, weight_src, mix_factor);
1038
1039         CLAMP(weight_src, 0.0f, 1.0f);
1040
1041         if (!dw_dst) {
1042                 defvert_add_index_notest(data_dst, idx_dst, weight_src);
1043         }
1044         else {
1045                 dw_dst->weight = weight_src;
1046         }
1047 }
1048
1049 static bool data_transfer_layersmapping_vgroups_multisrc_to_dst(
1050         ListBase *r_map, const int mix_mode, const float mix_factor, const float *mix_weights,
1051         const int num_elem_dst, const bool use_create, const bool use_delete,
1052         Object *ob_src, Object *ob_dst, MDeformVert *data_src, MDeformVert *data_dst,
1053         CustomData *UNUSED(cd_src), CustomData *cd_dst, const bool UNUSED(use_dupref_dst),
1054         const int tolayers, bool *use_layers_src, const int num_layers_src)
1055 {
1056         int idx_src;
1057         int idx_dst;
1058         int tot_dst = BLI_listbase_count(&ob_dst->defbase);
1059
1060         const size_t elem_size = sizeof(*((MDeformVert *)NULL));
1061
1062         switch (tolayers) {
1063                 case DT_LAYERS_INDEX_DST:
1064                         idx_dst = tot_dst;
1065
1066                         /* Find last source actually used! */
1067                         idx_src = num_layers_src;
1068                         while (idx_src-- && !use_layers_src[idx_src]);
1069                         idx_src++;
1070
1071                         if (idx_dst < idx_src) {
1072                                 if (!use_create) {
1073                                         return false;
1074                                 }
1075                                 /* Create as much vgroups as necessary! */
1076                                 for (; idx_dst < idx_src; idx_dst++) {
1077                                         BKE_object_defgroup_add(ob_dst);
1078                                 }
1079                         }
1080                         else if (use_delete && idx_dst > idx_src) {
1081                                 while (idx_dst-- > idx_src) {
1082                                         BKE_object_defgroup_remove(ob_dst, ob_dst->defbase.last);
1083                                 }
1084                         }
1085                         if (r_map) {
1086                                 /* At this stage, we **need** a valid CD_MDEFORMVERT layer on dest!
1087                                  * Again, use_create is not relevant in this case */
1088                                 if (!data_dst) {
1089                                         data_dst = CustomData_add_layer(cd_dst, CD_MDEFORMVERT, CD_CALLOC, NULL, num_elem_dst);
1090                                 }
1091
1092                                 while (idx_src--) {
1093                                         if (!use_layers_src[idx_src]) {
1094                                                 continue;
1095                                         }
1096                                         data_transfer_layersmapping_add_item(r_map, CD_FAKE_MDEFORMVERT, mix_mode, mix_factor, mix_weights,
1097                                                                              data_src, data_dst, idx_src, idx_src,
1098                                                                              elem_size, 0, 0, 0, vgroups_datatransfer_interp, NULL);
1099                                 }
1100                         }
1101                         break;
1102                 case DT_LAYERS_NAME_DST:
1103                         {
1104                                 bDeformGroup *dg_src, *dg_dst;
1105
1106                                 if (use_delete) {
1107                                         /* Remove all unused dst vgroups first, simpler in this case. */
1108                                         for (dg_dst = ob_dst->defbase.first; dg_dst;) {
1109                                                 bDeformGroup *dg_dst_next = dg_dst->next;
1110
1111                                                 if (defgroup_name_index(ob_src, dg_dst->name) == -1) {
1112                                                         BKE_object_defgroup_remove(ob_dst, dg_dst);
1113                                                 }
1114                                                 dg_dst = dg_dst_next;
1115                                         }
1116                                 }
1117
1118                                 for (idx_src = 0, dg_src = ob_src->defbase.first;
1119                                      idx_src < num_layers_src;
1120                                      idx_src++, dg_src = dg_src->next)
1121                                 {
1122                                         if (!use_layers_src[idx_src]) {
1123                                                 continue;
1124                                         }
1125
1126                                         if ((idx_dst = defgroup_name_index(ob_dst, dg_src->name)) == -1) {
1127                                                 if (!use_create) {
1128                                                         if (r_map) {
1129                                                                 BLI_freelistN(r_map);
1130                                                         }
1131                                                         return false;
1132                                                 }
1133                                                 BKE_object_defgroup_add_name(ob_dst, dg_src->name);
1134                                                 idx_dst = ob_dst->actdef - 1;
1135                                         }
1136                                         if (r_map) {
1137                                                 /* At this stage, we **need** a valid CD_MDEFORMVERT layer on dest!
1138                                                  * use_create is not relevant in this case */
1139                                                 if (!data_dst) {
1140                                                         data_dst = CustomData_add_layer(cd_dst, CD_MDEFORMVERT, CD_CALLOC, NULL, num_elem_dst);
1141                                                 }
1142
1143                                                 data_transfer_layersmapping_add_item(
1144                                                         r_map, CD_FAKE_MDEFORMVERT, mix_mode, mix_factor, mix_weights,
1145                                                         data_src, data_dst, idx_src, idx_dst,
1146                                                         elem_size, 0, 0, 0, vgroups_datatransfer_interp, NULL);
1147                                         }
1148                                 }
1149                                 break;
1150                         }
1151                 default:
1152                         return false;
1153         }
1154
1155         return true;
1156 }
1157
1158 bool data_transfer_layersmapping_vgroups(
1159         ListBase *r_map, const int mix_mode, const float mix_factor, const float *mix_weights,
1160         const int num_elem_dst, const bool use_create, const bool use_delete, Object *ob_src, Object *ob_dst,
1161         CustomData *cd_src, CustomData *cd_dst, const bool use_dupref_dst, const int fromlayers, const int tolayers)
1162 {
1163         int idx_src, idx_dst;
1164         MDeformVert *data_src, *data_dst = NULL;
1165
1166         const size_t elem_size = sizeof(*((MDeformVert *)NULL));
1167
1168         /* Note: VGroups are a bit hairy, since their layout is defined on object level (ob->defbase), while their actual
1169          *       data is a (mesh) CD layer.
1170          *       This implies we may have to handle data layout itself while having NULL data itself,
1171          *       and even have to support NULL data_src in transfer data code (we always create a data_dst, though).
1172          */
1173
1174         if (BLI_listbase_is_empty(&ob_src->defbase)) {
1175                 if (use_delete) {
1176                         BKE_object_defgroup_remove_all(ob_dst);
1177                 }
1178                 return true;
1179         }
1180
1181         data_src = CustomData_get_layer(cd_src, CD_MDEFORMVERT);
1182
1183         data_dst = CustomData_get_layer(cd_dst, CD_MDEFORMVERT);
1184         if (data_dst && use_dupref_dst && r_map) {
1185                 /* If dest is a derivedmesh, we do not want to overwrite cdlayers of org mesh! */
1186                 data_dst = CustomData_duplicate_referenced_layer(cd_dst, CD_MDEFORMVERT, num_elem_dst);
1187         }
1188
1189         if (fromlayers == DT_LAYERS_ACTIVE_SRC || fromlayers >= 0) {
1190                 /* Note: use_delete has not much meaning in this case, ignored. */
1191
1192                 if (fromlayers >= 0) {
1193                         idx_src = fromlayers;
1194                         if (idx_src >= BLI_listbase_count(&ob_src->defbase)) {
1195                                 /* This can happen when vgroups are removed from source object...
1196                                  * Remapping would be really tricky here, we'd need to go over all objects in Main everytime we delete
1197                                  * a vgroup... for now, simpler and safer to abort. */
1198                                 return false;
1199                         }
1200                 }
1201                 else if ((idx_src = ob_src->actdef - 1) == -1) {
1202                         return false;
1203                 }
1204
1205                 if (tolayers >= 0) {
1206                         /* Note: in this case we assume layer exists! */
1207                         idx_dst = tolayers;
1208                         BLI_assert(idx_dst < BLI_listbase_count(&ob_dst->defbase));
1209                 }
1210                 else if (tolayers == DT_LAYERS_ACTIVE_DST) {
1211                         if ((idx_dst = ob_dst->actdef - 1) == -1) {
1212                                 bDeformGroup *dg_src;
1213                                 if (!use_create) {
1214                                         return true;
1215                                 }
1216                                 dg_src = BLI_findlink(&ob_src->defbase, idx_src);
1217                                 BKE_object_defgroup_add_name(ob_dst, dg_src->name);
1218                                 idx_dst = ob_dst->actdef - 1;
1219                         }
1220                 }
1221                 else if (tolayers == DT_LAYERS_INDEX_DST) {
1222                         int num = BLI_listbase_count(&ob_src->defbase);
1223                         idx_dst = idx_src;
1224                         if (num <= idx_dst) {
1225                                 if (!use_create) {
1226                                         return true;
1227                                 }
1228                                 /* Create as much vgroups as necessary! */
1229                                 for (; num <= idx_dst; num++) {
1230                                         BKE_object_defgroup_add(ob_dst);
1231                                 }
1232                         }
1233                 }
1234                 else if (tolayers == DT_LAYERS_NAME_DST) {
1235                         bDeformGroup *dg_src = BLI_findlink(&ob_src->defbase, idx_src);
1236                         if ((idx_dst = defgroup_name_index(ob_dst, dg_src->name)) == -1) {
1237                                 if (!use_create) {
1238                                         return true;
1239                                 }
1240                                 BKE_object_defgroup_add_name(ob_dst, dg_src->name);
1241                                 idx_dst = ob_dst->actdef - 1;
1242                         }
1243                 }
1244                 else {
1245                         return false;
1246                 }
1247
1248                 if (r_map) {
1249                         /* At this stage, we **need** a valid CD_MDEFORMVERT layer on dest!
1250                          * use_create is not relevant in this case */
1251                         if (!data_dst) {
1252                                 data_dst = CustomData_add_layer(cd_dst, CD_MDEFORMVERT, CD_CALLOC, NULL, num_elem_dst);
1253                         }
1254
1255                         data_transfer_layersmapping_add_item(r_map, CD_FAKE_MDEFORMVERT, mix_mode, mix_factor, mix_weights,
1256                                                              data_src, data_dst, idx_src, idx_dst,
1257                                                              elem_size, 0, 0, 0, vgroups_datatransfer_interp, NULL);
1258                 }
1259         }
1260         else {
1261                 int num_src, num_sel_unused;
1262                 bool *use_layers_src = NULL;
1263                 bool ret = false;
1264
1265                 switch (fromlayers) {
1266                         case DT_LAYERS_ALL_SRC:
1267                                 use_layers_src = BKE_object_defgroup_subset_from_select_type(ob_src, WT_VGROUP_ALL,
1268                                                                                              &num_src, &num_sel_unused);
1269                                 break;
1270                         case DT_LAYERS_VGROUP_SRC_BONE_SELECT:
1271                                 use_layers_src = BKE_object_defgroup_subset_from_select_type(ob_src, WT_VGROUP_BONE_SELECT,
1272                                                                                              &num_src, &num_sel_unused);
1273                                 break;
1274                         case DT_LAYERS_VGROUP_SRC_BONE_DEFORM:
1275                                 use_layers_src = BKE_object_defgroup_subset_from_select_type(ob_src, WT_VGROUP_BONE_DEFORM,
1276                                                                                              &num_src, &num_sel_unused);
1277                                 break;
1278                 }
1279
1280                 if (use_layers_src) {
1281                         ret = data_transfer_layersmapping_vgroups_multisrc_to_dst(
1282                                 r_map, mix_mode, mix_factor, mix_weights, num_elem_dst, use_create, use_delete,
1283                                 ob_src, ob_dst, data_src, data_dst, cd_src, cd_dst, use_dupref_dst,
1284                                 tolayers, use_layers_src, num_src);
1285                 }
1286
1287                 MEM_SAFE_FREE(use_layers_src);
1288                 return ret;
1289         }
1290
1291         return true;
1292 }
1293
1294 /** \} */
1295
1296 /* -------------------------------------------------------------------- */
1297 /** \name Various utils & helpers.
1298  * \{ */
1299
1300 void BKE_defvert_weight_to_rgb(float r_rgb[3], const float weight)
1301 {
1302         const float blend = ((weight / 2.0f) + 0.5f);
1303
1304         if (weight <= 0.25f) {    /* blue->cyan */
1305                 r_rgb[0] = 0.0f;
1306                 r_rgb[1] = blend * weight * 4.0f;
1307                 r_rgb[2] = blend;
1308         }
1309         else if (weight <= 0.50f) {  /* cyan->green */
1310                 r_rgb[0] = 0.0f;
1311                 r_rgb[1] = blend;
1312                 r_rgb[2] = blend * (1.0f - ((weight - 0.25f) * 4.0f));
1313         }
1314         else if (weight <= 0.75f) {  /* green->yellow */
1315                 r_rgb[0] = blend * ((weight - 0.50f) * 4.0f);
1316                 r_rgb[1] = blend;
1317                 r_rgb[2] = 0.0f;
1318         }
1319         else if (weight <= 1.0f) {  /* yellow->red */
1320                 r_rgb[0] = blend;
1321                 r_rgb[1] = blend * (1.0f - ((weight - 0.75f) * 4.0f));
1322                 r_rgb[2] = 0.0f;
1323         }
1324         else {
1325                 /* exceptional value, unclamped or nan,
1326                  * avoid uninitialized memory use */
1327                 r_rgb[0] = 1.0f;
1328                 r_rgb[1] = 0.0f;
1329                 r_rgb[2] = 1.0f;
1330         }
1331 }
1332
1333 /** \} */