svn merge -r40166:40279 ^/trunk/blender
[blender.git] / source / blender / modifiers / intern / MOD_weightvgproximity.c
index 06ffb5e451055db7996bc3a300670bb8efae57ea..6242c6d0f5e9f914c86624d5ef2128b7efcb67aa 100644 (file)
@@ -1,42 +1,42 @@
 /*
-* $Id$
-*
-* ***** BEGIN GPL LICENSE BLOCK *****
-*
-* This program is free software; you can redistribute it and/or
-* modify it under the terms of the GNU General Public License
-* as published by the Free Software Foundation; either version 2
-* of the License, or (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-* GNU General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with this program; if not, write to the Free Software  Foundation,
-* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-*
-* The Original Code is Copyright (C) 2011 by Bastien Montagne.
-* All rights reserved.
-*
-* Contributor(s): None yet.
-*
-* ***** END GPL LICENSE BLOCK *****
-*
-*/
+ * $Id$
+ *
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software  Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2011 by Bastien Montagne.
+ * All rights reserved.
+ *
+ * Contributor(s): None yet.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ *
+ */
 
 /*
- * XXX Id like to make modified weights visible in WeightPaint mode,
- *     but couldn’t figure a way to do this…
+ * XXX I'd like to make modified weights visible in WeightPaint mode,
+ *     but couldn't figure a way to do this...
  *     Maybe this will need changes in mesh_calc_modifiers (DerivedMesh.c)?
  *     Or the WeightPaint mode code itself?
  */
 
-#include "BLI_utildefines.h"
+#include "BLI_editVert.h"
 #include "BLI_math.h"
 #include "BLI_string.h"
-#include "BLI_editVert.h"
+#include "BLI_utildefines.h"
 
 #include "DNA_mesh_types.h"
 #include "DNA_meshdata_types.h"
 /* Util macro. */
 #define OUT_OF_MEMORY() ((void)printf("WeightVGProximity: Out of memory.\n"))
 
-/**
- * Returns the squared distance between two given points.
- */
-static float squared_dist(const float *a, const float *b)
-{
-       float tmp[3];
-       VECSUB(tmp, a, b);
-       return INPR(tmp, tmp);
-}
-
 /**
  * Find nearest vertex and/or edge and/or face, for each vertex (adapted from shrinkwrap.c).
  */
@@ -126,7 +116,7 @@ static void get_vert2geom_distance(int numVerts, float (*v_cos)[3],
                float tmp_co[3];
 
                /* Convert the vertex to tree coordinates. */
-               VECCOPY(tmp_co, v_cos[i]);
+               copy_v3_v3(tmp_co, v_cos[i]);
                space_transform_apply(loc2trgt, tmp_co);
 
                /* Use local proximity heuristics (to reduce the nearest search).
@@ -137,19 +127,19 @@ static void get_vert2geom_distance(int numVerts, float (*v_cos)[3],
                 * This will lead in prunning of the search tree.
                 */
                if (dist_v) {
-                       nearest_v.dist = nearest_v.index != -1 ? squared_dist(tmp_co, nearest_v.co) : FLT_MAX;
+                       nearest_v.dist = nearest_v.index != -1 ? len_squared_v3v3(tmp_co, nearest_v.co) : FLT_MAX;
                        /* Compute and store result. If invalid (-1 idx), keep FLT_MAX dist. */
                        BLI_bvhtree_find_nearest(treeData_v.tree, tmp_co, &nearest_v, treeData_v.nearest_callback, &treeData_v);
                        dist_v[i] = sqrtf(nearest_v.dist);
                }
                if (dist_e) {
-                       nearest_e.dist = nearest_e.index != -1 ? squared_dist(tmp_co, nearest_e.co) : FLT_MAX;
+                       nearest_e.dist = nearest_e.index != -1 ? len_squared_v3v3(tmp_co, nearest_e.co) : FLT_MAX;
                        /* Compute and store result. If invalid (-1 idx), keep FLT_MAX dist. */
                        BLI_bvhtree_find_nearest(treeData_e.tree, tmp_co, &nearest_e, treeData_e.nearest_callback, &treeData_e);
                        dist_e[i] = sqrtf(nearest_e.dist);
                }
                if (dist_f) {
-                       nearest_f.dist = nearest_f.index != -1 ? squared_dist(tmp_co, nearest_f.co) : FLT_MAX;
+                       nearest_f.dist = nearest_f.index != -1 ? len_squared_v3v3(tmp_co, nearest_f.co) : FLT_MAX;
                        /* Compute and store result. If invalid (-1 idx), keep FLT_MAX dist. */
                        BLI_bvhtree_find_nearest(treeData_f.tree, tmp_co, &nearest_f, treeData_f.nearest_callback, &treeData_f);
                        dist_f[i] = sqrtf(nearest_f.dist);
@@ -169,25 +159,17 @@ static void get_vert2geom_distance(int numVerts, float (*v_cos)[3],
  * Note that it works in final world space (i.e. with constraints etc. applied).
  */
 static void get_vert2ob_distance(int numVerts, float (*v_cos)[3], float *dist,
-                                 const Object* ob, const Object* obr)
+                                 Object* ob, Object* obr)
 {
        /* Vertex and ref object coordinates. */
-       float v_wco[3],
-             or_wco[3],
-             or_wro[3][3], /*unused*/
-             or_wsz[3];    /*unused*/
-       int i;
+       float v_wco[3];
+       unsigned int i= numVerts;
 
-       /* Get world-coordinates of the reference object (constraints and anim included).
-        * We also get rotation and scale, even though we do not want them…
-        */
-       mat4_to_loc_rot_size(or_wco, or_wro, or_wsz, (float (*)[4])obr->obmat);
-
-       for (i = 0; i < numVerts; i++) {
+       while(i-- > 0) {
                /* Get world-coordinates of the vertex (constraints and anim included). */
-               mul_v3_m4v3(v_wco, (float (*)[4])ob->obmat, v_cos[i]);
+               mul_v3_m4v3(v_wco, ob->obmat, v_cos[i]);
                /* Return distance between both coordinates. */
-               dist[i] = len_v3v3(v_wco, or_wco);
+               dist[i] = len_v3v3(v_wco, obr->obmat[3]);
        }
 }
 
@@ -200,6 +182,38 @@ static float get_ob2ob_distance(const Object* ob, const Object* obr)
        return len_v3v3(ob->obmat[3], obr->obmat[3]); 
 }
 
+/**
+ * Maps distances to weights, with an optionnal “smoothing” mapping.
+ */
+void do_map(float *weights, const int nidx, const float min_d, const float max_d, short mode)
+{
+       const float range_inv= 1.0f / (max_d - min_d); /* invert since multiplication is faster */
+       unsigned int i= nidx;
+       if(max_d == min_d) {
+               while (i-- > 0) {
+                       weights[i] = (weights[i] >= max_d) ? 1.0f : 0.0f; /* "Step" behavior... */
+               }
+       }
+       else if(max_d > min_d) {
+               while (i-- > 0) {
+                       if     (weights[i] >= max_d) weights[i]= 1.0f; /* most likely case first */
+                       else if(weights[i] <= min_d) weights[i]= 0.0f;
+                       else                         weights[i]= (weights[i] - min_d) * range_inv;
+               }
+       }
+       else {
+               while (i-- > 0) {
+                       if     (weights[i] <= max_d) weights[i]= 1.0f; /* most likely case first */
+                       else if(weights[i] >= min_d) weights[i]= 0.0f;
+                       else                         weights[i]= (weights[i] - min_d) * range_inv;
+               }
+       }
+
+       if(!ELEM(mode, MOD_WVG_MAPPING_NONE, MOD_WVG_MAPPING_CURVE)) {
+               weightvg_do_map(nidx, weights, mode, NULL);
+       }
+}
+
 /**************************************
  * Modifiers functions.               *
  **************************************/
@@ -207,12 +221,15 @@ static void initData(ModifierData *md)
 {
        WeightVGProximityModifierData *wmd = (WeightVGProximityModifierData*) md;
 
-       wmd->proximity_mode       = MOD_WVG_PROXIMITY_OBJ2OBJDIST;
-       wmd->proximity_flags      = MOD_WVG_PROXIMITY_O2VD_VERTS;
+       wmd->proximity_mode       = MOD_WVG_PROXIMITY_OBJECT;
+       wmd->proximity_flags      = MOD_WVG_PROXIMITY_GEOM_VERTS;
+
+       wmd->falloff_type         = MOD_WVG_MAPPING_NONE;
 
        wmd->mask_constant        = 1.0f;
        wmd->mask_tex_use_channel = MOD_WVG_MASK_TEX_USE_INT; /* Use intensity by default. */
        wmd->mask_tex_mapping     = MOD_DISP_MAP_LOCAL;
+       wmd->max_dist             = 1.0f; /* vert arbitrary distance, but don't use 0 */
 }
 
 static void copyData(ModifierData *md, ModifierData *target)
@@ -225,14 +242,17 @@ static void copyData(ModifierData *md, ModifierData *target)
        twmd->proximity_flags        = wmd->proximity_flags;
        twmd->proximity_ob_target    = wmd->proximity_ob_target;
 
+       twmd->falloff_type           = wmd->falloff_type;
+
        twmd->mask_constant          = wmd->mask_constant;
        BLI_strncpy(twmd->mask_defgrp_name, wmd->mask_defgrp_name, sizeof(twmd->mask_defgrp_name));
        twmd->mask_texture           = wmd->mask_texture;
        twmd->mask_tex_use_channel   = wmd->mask_tex_use_channel;
        twmd->mask_tex_mapping       = wmd->mask_tex_mapping;
        twmd->mask_tex_map_obj       = wmd->mask_tex_map_obj;
-       BLI_strncpy(twmd->mask_tex_uvlayer_name, wmd->mask_tex_uvlayer_name,
-                   sizeof(twmd->mask_tex_uvlayer_name));
+       BLI_strncpy(twmd->mask_tex_uvlayer_name, wmd->mask_tex_uvlayer_name, sizeof(twmd->mask_tex_uvlayer_name));
+       twmd->min_dist               = wmd->min_dist;
+       twmd->max_dist               = wmd->max_dist;
 }
 
 static CustomDataMask requiredDataMask(Object *UNUSED(ob), ModifierData *md)
@@ -277,6 +297,11 @@ static void foreachIDLink(ModifierData *md, Object *ob, IDWalkFunc walk, void *u
        foreachObjectLink(md, ob, (ObjectWalkFunc)walk, userData);
 }
 
+static void foreachTexLink(ModifierData *md, Object *ob, TexWalkFunc walk, void *userData)
+{
+       walk(userData, ob, md, "mask_texture");
+}
+
 static void updateDepgraph(ModifierData *md, DagForest *forest, struct Scene *UNUSED(scene),
                            Object *UNUSED(ob), DagNode *obNode)
 {
@@ -305,7 +330,7 @@ static int isDisabled(ModifierData *md, int UNUSED(useRenderParams))
 {
        WeightVGProximityModifierData *wmd = (WeightVGProximityModifierData*) md;
        /* If no vertex group, bypass. */
-       if (wmd->defgrp_name == NULL) return 1;
+       if (wmd->defgrp_name[0] == '\0') return 1;
        /* If no target object, bypass. */
        return (wmd->proximity_ob_target == NULL);
 }
@@ -314,19 +339,22 @@ static DerivedMesh *applyModifier(ModifierData *md, Object *ob, DerivedMesh *der
                                   int UNUSED(useRenderParams), int UNUSED(isFinalCalc))
 {
        WeightVGProximityModifierData *wmd = (WeightVGProximityModifierData*) md;
-       DerivedMesh *dm = derivedData;
-       DerivedMesh *ret;
+       DerivedMesh *dm = derivedData, *ret = NULL;
+#if 0
+       Mesh *ob_m = NULL;
+#endif
        MDeformVert *dvert = NULL;
+       MDeformWeight **dw, **tdw;
        int numVerts;
        float (*v_cos)[3] = NULL; /* The vertices coordinates. */
-       Object *obr; /* Our target object. */
+       Object *obr = NULL; /* Our target object. */
        int defgrp_idx;
        float *tw = NULL;
        float *org_w = NULL;
        float *new_w =NULL;
        int *tidx, *indices = NULL;
        int numIdx = 0;
-       int i, j;
+       int i;
        char rel_ret = 0; /* Boolean, whether we have to release ret dm or not, when not using it! */
 
        /* Get number of verts. */
@@ -338,53 +366,92 @@ static DerivedMesh *applyModifier(ModifierData *md, Object *ob, DerivedMesh *der
        if ((numVerts == 0) || (ob->defbase.first == NULL))
                return dm;
 
-       /* Create a copy of our dmesh.
-        * TODO: This should be done only if needed !
+       /* Get our target object. */
+       obr = wmd->proximity_ob_target;
+       if (obr == NULL)
+               return dm;
+
+       /* Get vgroup idx from its name. */
+       defgrp_idx = defgroup_name_index(ob, wmd->defgrp_name);
+       if (defgrp_idx < 0)
+               return dm;
+
+       /* XXX All this to avoid copying dm when not needed... However, it nearly doubles compute
+        *     time! See scene 5 of the WeighVG test file...
+        */
+#if 0
+       /* Get actual dverts (ie vertex group data). */
+       dvert = dm->getVertDataArray(dm, CD_MDEFORMVERT);
+       /* If no dverts, return unmodified data... */
+       if (dvert == NULL)
+               return dm;
+
+       /* Get org mesh, only to test whether affected cdata layer has already been copied
+        * somewhere up in the modifiers stack.
         */
-       if (1) {
-               /* XXX Seems to create problems with weightpaint mode... */
+       ob_m = get_mesh(ob);
+       if (ob_m == NULL)
+               return dm;
+
+       /* Create a copy of our dmesh, only if our affected cdata layer is the same as org mesh. */
+       if (dvert == CustomData_get_layer(&ob_m->vdata, CD_MDEFORMVERT)) {
+               /* XXX Seems to create problems with weightpaint mode???
+                *     I'm missing something here, I guess...
+                */
 //             DM_set_only_copy(dm, CD_MASK_MDEFORMVERT); /* Only copy defgroup layer. */
                ret = CDDM_copy(dm);
+               dvert = ret->getVertDataArray(ret, CD_MDEFORMVERT);
+               if (dvert == NULL) {
+                       ret->release(ret);
+                       return dm;
+               }
                rel_ret = 1;
        }
        else
                ret = dm;
-
-       /* Get vgroup idx from its name. */
-       defgrp_idx = defgroup_name_index(ob, wmd->defgrp_name);
-
-       /* Get actual dverts (ie vertex group data). */
-       if (defgrp_idx >= 0)
-               dvert = ret->getVertDataArray(ret, CD_MDEFORMVERT);
-       /* Get our target object. */
-       obr = wmd->proximity_ob_target;
-       /* If no dverts or target object, return unmodified data… */
-       if ((defgrp_idx < 0) || (dvert == NULL) || (!obr)) {
+#else
+       ret = CDDM_copy(dm, 0);
+       rel_ret = 1;
+       dvert = ret->getVertDataArray(ret, CD_MDEFORMVERT);
+       if (dvert == NULL) {
                if (rel_ret)
                        ret->release(ret);
                return dm;
        }
+#endif
 
        /* Find out which vertices to work on (all vertices in vgroup), and get their relevant weight.
         */
        tidx = MEM_mallocN(sizeof(int) * numVerts, "WeightVGProximity Modifier, tidx");
        tw = MEM_mallocN(sizeof(float) * numVerts, "WeightVGProximity Modifier, tw");
+       tdw = MEM_mallocN(sizeof(MDeformWeight*) * numVerts, "WeightVGProximity Modifier, tdw");
        for (i = 0; i < numVerts; i++) {
-               for (j = 0; j < dvert[i].totweight; j++) {
-                       if(dvert[i].dw[j].def_nr == defgrp_idx) {
-                               tidx[numIdx] = i;
-                               tw[numIdx++] = dvert[i].dw[j].weight;
-                               break;
-                       }
+               MDeformWeight *_dw = defvert_find_index(&dvert[i], defgrp_idx);
+               if(_dw) {
+                       tidx[numIdx] = i;
+                       tw[numIdx] = _dw->weight;
+                       tdw[numIdx++] = _dw;
                }
        }
+       /* If no vertices found, return org data! */
+       if(numIdx == 0) {
+               MEM_freeN(tidx);
+               MEM_freeN(tw);
+               MEM_freeN(tdw);
+               if (rel_ret)
+                       ret->release(ret);
+               return dm;
+       }
        indices = MEM_mallocN(sizeof(int) * numIdx, "WeightVGProximity Modifier, indices");
        memcpy(indices, tidx, sizeof(int) * numIdx);
        org_w = MEM_mallocN(sizeof(float) * numIdx, "WeightVGProximity Modifier, org_w");
        new_w = MEM_mallocN(sizeof(float) * numIdx, "WeightVGProximity Modifier, new_w");
        memcpy(org_w, tw, sizeof(float) * numIdx);
+       dw = MEM_mallocN(sizeof(MDeformWeight*) * numIdx, "WeightVGProximity Modifier, dw");
+       memcpy(dw, tdw, sizeof(MDeformWeight*) * numIdx);
        MEM_freeN(tidx);
        MEM_freeN(tw);
+       MEM_freeN(tdw);
 
        /* Get our vertex coordinates. */
        v_cos = MEM_mallocN(sizeof(float[3]) * numIdx, "WeightVGProximity Modifier, v_cos");
@@ -392,15 +459,15 @@ static DerivedMesh *applyModifier(ModifierData *md, Object *ob, DerivedMesh *der
                ret->getVertCo(ret, indices[i], v_cos[i]);
 
        /* Compute wanted distances. */
-       if (wmd->proximity_mode == MOD_WVG_PROXIMITY_OBJ2OBJDIST) {
-               float dist = get_ob2ob_distance(ob, obr);
+       if (wmd->proximity_mode == MOD_WVG_PROXIMITY_OBJECT) {
+               const float dist = get_ob2ob_distance(ob, obr);
                for(i = 0; i < numIdx; i++)
                        new_w[i] = dist;
        }
-       else if (wmd->proximity_mode == MOD_WVG_PROXIMITY_OBJ2VERTDIST) {
-               char use_trgt_verts = (wmd->proximity_flags & MOD_WVG_PROXIMITY_O2VD_VERTS);
-               char use_trgt_edges = (wmd->proximity_flags & MOD_WVG_PROXIMITY_O2VD_EDGES);
-               char use_trgt_faces = (wmd->proximity_flags & MOD_WVG_PROXIMITY_O2VD_FACES);
+       else if (wmd->proximity_mode == MOD_WVG_PROXIMITY_GEOMETRY) {
+               const short use_trgt_verts = (wmd->proximity_flags & MOD_WVG_PROXIMITY_GEOM_VERTS);
+               const short use_trgt_edges = (wmd->proximity_flags & MOD_WVG_PROXIMITY_GEOM_EDGES);
+               const short use_trgt_faces = (wmd->proximity_flags & MOD_WVG_PROXIMITY_GEOM_FACES);
 
                if (use_trgt_verts || use_trgt_edges || use_trgt_faces) {
                        DerivedMesh *target_dm = obr->derivedFinal;
@@ -409,8 +476,8 @@ static DerivedMesh *applyModifier(ModifierData *md, Object *ob, DerivedMesh *der
                                        target_dm = CDDM_from_curve(obr);
                                else if (obr->type == OB_MESH) {
                                        Mesh *me = (Mesh*)obr->data;
-                                       if (me->edit_mesh)
-                                               target_dm = CDDM_from_editmesh((EditMesh*)me->edit_mesh, me);
+                                       if (me->edit_btmesh)
+                                               target_dm = CDDM_from_BMEditMesh(me->edit_btmesh, me, 0);
                                        else
                                                target_dm = CDDM_from_mesh(me, obr);
                                }
@@ -428,42 +495,49 @@ static DerivedMesh *applyModifier(ModifierData *md, Object *ob, DerivedMesh *der
                                                       target_dm, &loc2trgt);
                                for(i = 0; i < numIdx; i++) {
                                        new_w[i] = dists_v ? dists_v[i] : FLT_MAX;
-                                       new_w[i] = dists_e ? minf(dists_e[i], new_w[i]) : new_w[i];
-                                       new_w[i] = dists_f ? minf(dists_f[i], new_w[i]) : new_w[i];
+                                       if(dists_e)
+                                               new_w[i] = minf(dists_e[i], new_w[i]);
+                                       if(dists_f)
+                                               new_w[i] = minf(dists_f[i], new_w[i]);
                                }
+                               if(dists_v) MEM_freeN(dists_v);
+                               if(dists_e) MEM_freeN(dists_e);
+                               if(dists_f) MEM_freeN(dists_f);
                        }
                        /* Else, fall back to default obj2vert behavior. */
-                       else
+                       else {
                                get_vert2ob_distance(numIdx, v_cos, new_w, ob, obr);
+                       }
                }
-               else
+               else {
                        get_vert2ob_distance(numIdx, v_cos, new_w, ob, obr);
+               }
        }
 
+       /* Map distances to weights. */
+       do_map(new_w, numIdx, wmd->min_dist, wmd->max_dist, wmd->falloff_type);
+
        /* Do masking. */
        weightvg_do_mask(numIdx, indices, org_w, new_w, ob, ret, wmd->mask_constant,
                         wmd->mask_defgrp_name, wmd->mask_texture, wmd->mask_tex_use_channel,
                         wmd->mask_tex_mapping, wmd->mask_tex_map_obj, wmd->mask_tex_uvlayer_name);
 
        /* Update vgroup. Note we never add nor remove vertices from vgroup here. */
-       weightvg_update_vg(dvert, defgrp_idx, numIdx, indices, org_w, 0, 0.0f, 0, 0.0f);
+       weightvg_update_vg(dvert, defgrp_idx, dw, numIdx, indices, org_w, FALSE, 0.0f, FALSE, 0.0f);
 
        /* Freeing stuff. */
-       if (org_w)
-               MEM_freeN(org_w);
-       if (new_w)
-               MEM_freeN(new_w);
-       if (indices)
-               MEM_freeN(indices);
-       if (v_cos)
-               MEM_freeN(v_cos);
+       MEM_freeN(org_w);
+       MEM_freeN(new_w);
+       MEM_freeN(dw);
+       MEM_freeN(indices);
+       MEM_freeN(v_cos);
 
        /* Return the vgroup-modified mesh. */
        return ret;
 }
 
 static DerivedMesh *applyModifierEM(ModifierData *md, Object *ob,
-                                    struct EditMesh *UNUSED(editData),
+                                    struct BMEditMesh *UNUSED(editData),
                                     DerivedMesh *derivedData)
 {
        return applyModifier(md, ob, derivedData, 0, 1);
@@ -471,12 +545,12 @@ static DerivedMesh *applyModifierEM(ModifierData *md, Object *ob,
 
 
 ModifierTypeInfo modifierType_WeightVGProximity = {
-       /* name */              "WeightVGProximity",
+       /* name */              "VertexWeightProximity",
        /* structName */        "WeightVGProximityModifierData",
        /* structSize */        sizeof(WeightVGProximityModifierData),
        /* type */              eModifierTypeType_Nonconstructive,
        /* flags */             eModifierTypeFlag_AcceptsMesh
-                              |eModifierTypeFlag_SupportsMapping
+/*                            |eModifierTypeFlag_SupportsMapping*/
                               |eModifierTypeFlag_SupportsEditmode,
 
        /* copyData */          copyData,
@@ -495,5 +569,5 @@ ModifierTypeInfo modifierType_WeightVGProximity = {
        /* dependsOnNormals */  NULL,
        /* foreachObjectLink */ foreachObjectLink,
        /* foreachIDLink */     foreachIDLink,
+       /* foreachTexLink */    foreachTexLink,
 };
-