Blender V2.61 - r43446
|
00001 /* 00002 * ***** BEGIN GPL LICENSE BLOCK ***** 00003 * 00004 * This program is free software; you can redistribute it and/or 00005 * modify it under the terms of the GNU General Public License 00006 * as published by the Free Software Foundation; either version 2 00007 * of the License, or (at your option) any later version. 00008 * 00009 * This program is distributed in the hope that it will be useful, 00010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 * GNU General Public License for more details. 00013 * 00014 * You should have received a copy of the GNU General Public License 00015 * along with this program; if not, write to the Free Software Foundation, 00016 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00017 * 00018 * The Original Code is Copyright (C) 2005 by the Blender Foundation. 00019 * All rights reserved. 00020 * 00021 * Contributor(s): Daniel Dunbar 00022 * Ton Roosendaal, 00023 * Ben Batt, 00024 * Brecht Van Lommel, 00025 * Campbell Barton 00026 * 00027 * ***** END GPL LICENSE BLOCK ***** 00028 * 00029 */ 00030 00036 #include "DNA_meshdata_types.h" 00037 #include "DNA_object_types.h" 00038 00039 #include "BLI_math.h" 00040 #include "BLI_utildefines.h" 00041 #include "BLI_string.h" 00042 00043 00044 #include "BKE_deform.h" 00045 #include "BKE_DerivedMesh.h" 00046 #include "BKE_modifier.h" 00047 00048 00049 #include "depsgraph_private.h" 00050 00051 #include "MOD_util.h" 00052 00053 static void initData(ModifierData *md) 00054 { 00055 CastModifierData *cmd = (CastModifierData*) md; 00056 00057 cmd->fac = 0.5f; 00058 cmd->radius = 0.0f; 00059 cmd->size = 0.0f; 00060 cmd->flag = MOD_CAST_X | MOD_CAST_Y | MOD_CAST_Z 00061 | MOD_CAST_SIZE_FROM_RADIUS; 00062 cmd->type = MOD_CAST_TYPE_SPHERE; 00063 cmd->defgrp_name[0] = '\0'; 00064 cmd->object = NULL; 00065 } 00066 00067 00068 static void copyData(ModifierData *md, ModifierData *target) 00069 { 00070 CastModifierData *cmd = (CastModifierData*) md; 00071 CastModifierData *tcmd = (CastModifierData*) target; 00072 00073 tcmd->fac = cmd->fac; 00074 tcmd->radius = cmd->radius; 00075 tcmd->size = cmd->size; 00076 tcmd->flag = cmd->flag; 00077 tcmd->type = cmd->type; 00078 tcmd->object = cmd->object; 00079 BLI_strncpy(tcmd->defgrp_name, cmd->defgrp_name, sizeof(tcmd->defgrp_name)); 00080 } 00081 00082 static int isDisabled(ModifierData *md, int UNUSED(useRenderParams)) 00083 { 00084 CastModifierData *cmd = (CastModifierData*) md; 00085 short flag; 00086 00087 flag = cmd->flag & (MOD_CAST_X|MOD_CAST_Y|MOD_CAST_Z); 00088 00089 if((cmd->fac == 0.0f) || flag == 0) return 1; 00090 00091 return 0; 00092 } 00093 00094 static CustomDataMask requiredDataMask(Object *UNUSED(ob), ModifierData *md) 00095 { 00096 CastModifierData *cmd = (CastModifierData *)md; 00097 CustomDataMask dataMask = 0; 00098 00099 /* ask for vertexgroups if we need them */ 00100 if(cmd->defgrp_name[0]) dataMask |= CD_MASK_MDEFORMVERT; 00101 00102 return dataMask; 00103 } 00104 00105 static void foreachObjectLink( 00106 ModifierData *md, Object *ob, 00107 void (*walk)(void *userData, Object *ob, Object **obpoin), 00108 void *userData) 00109 { 00110 CastModifierData *cmd = (CastModifierData*) md; 00111 00112 walk (userData, ob, &cmd->object); 00113 } 00114 00115 static void updateDepgraph(ModifierData *md, DagForest *forest, 00116 struct Scene *UNUSED(scene), 00117 Object *UNUSED(ob), 00118 DagNode *obNode) 00119 { 00120 CastModifierData *cmd = (CastModifierData*) md; 00121 00122 if (cmd->object) { 00123 DagNode *curNode = dag_get_node(forest, cmd->object); 00124 00125 dag_add_relation(forest, curNode, obNode, DAG_RL_OB_DATA, 00126 "Cast Modifier"); 00127 } 00128 } 00129 00130 static void sphere_do( 00131 CastModifierData *cmd, Object *ob, DerivedMesh *dm, 00132 float (*vertexCos)[3], int numVerts) 00133 { 00134 MDeformVert *dvert = NULL; 00135 00136 Object *ctrl_ob = NULL; 00137 00138 int i, defgrp_index; 00139 int has_radius = 0; 00140 short flag, type; 00141 float fac, facm, len = 0.0f; 00142 float vec[3], center[3] = {0.0f, 0.0f, 0.0f}; 00143 float mat[4][4], imat[4][4]; 00144 00145 fac = cmd->fac; 00146 facm = 1.0f - fac; 00147 00148 flag = cmd->flag; 00149 type = cmd->type; /* projection type: sphere or cylinder */ 00150 00151 if (type == MOD_CAST_TYPE_CYLINDER) 00152 flag &= ~MOD_CAST_Z; 00153 00154 ctrl_ob = cmd->object; 00155 00156 /* spherify's center is {0, 0, 0} (the ob's own center in its local 00157 * space), by default, but if the user defined a control object, 00158 * we use its location, transformed to ob's local space */ 00159 if (ctrl_ob) { 00160 if(flag & MOD_CAST_USE_OB_TRANSFORM) { 00161 invert_m4_m4(ctrl_ob->imat, ctrl_ob->obmat); 00162 mult_m4_m4m4(mat, ctrl_ob->imat, ob->obmat); 00163 invert_m4_m4(imat, mat); 00164 } 00165 00166 invert_m4_m4(ob->imat, ob->obmat); 00167 mul_v3_m4v3(center, ob->imat, ctrl_ob->obmat[3]); 00168 } 00169 00170 /* now we check which options the user wants */ 00171 00172 /* 1) (flag was checked in the "if (ctrl_ob)" block above) */ 00173 /* 2) cmd->radius > 0.0f: only the vertices within this radius from 00174 * the center of the effect should be deformed */ 00175 if (cmd->radius > FLT_EPSILON) has_radius = 1; 00176 00177 /* 3) if we were given a vertex group name, 00178 * only those vertices should be affected */ 00179 modifier_get_vgroup(ob, dm, cmd->defgrp_name, &dvert, &defgrp_index); 00180 00181 if(flag & MOD_CAST_SIZE_FROM_RADIUS) { 00182 len = cmd->radius; 00183 } 00184 else { 00185 len = cmd->size; 00186 } 00187 00188 if(len <= 0) { 00189 for (i = 0; i < numVerts; i++) { 00190 len += len_v3v3(center, vertexCos[i]); 00191 } 00192 len /= numVerts; 00193 00194 if (len == 0.0f) len = 10.0f; 00195 } 00196 00197 /* ready to apply the effect, one vertex at a time; 00198 * tiny optimization: the code is separated (with parts repeated) 00199 * in two possible cases: 00200 * with or w/o a vgroup. With lots of if's in the code below, 00201 * further optimizations are possible, if needed */ 00202 if (dvert) { /* with a vgroup */ 00203 MDeformVert *dv= dvert; 00204 float fac_orig = fac; 00205 for (i = 0; i < numVerts; i++, dv++) { 00206 float tmp_co[3]; 00207 float weight; 00208 00209 copy_v3_v3(tmp_co, vertexCos[i]); 00210 if(ctrl_ob) { 00211 if(flag & MOD_CAST_USE_OB_TRANSFORM) { 00212 mul_m4_v3(mat, tmp_co); 00213 } else { 00214 sub_v3_v3(tmp_co, center); 00215 } 00216 } 00217 00218 copy_v3_v3(vec, tmp_co); 00219 00220 if (type == MOD_CAST_TYPE_CYLINDER) 00221 vec[2] = 0.0f; 00222 00223 if (has_radius) { 00224 if (len_v3(vec) > cmd->radius) continue; 00225 } 00226 00227 weight= defvert_find_weight(dv, defgrp_index); 00228 if (weight <= 0.0f) continue; 00229 00230 fac = fac_orig * weight; 00231 facm = 1.0f - fac; 00232 00233 normalize_v3(vec); 00234 00235 if (flag & MOD_CAST_X) 00236 tmp_co[0] = fac*vec[0]*len + facm*tmp_co[0]; 00237 if (flag & MOD_CAST_Y) 00238 tmp_co[1] = fac*vec[1]*len + facm*tmp_co[1]; 00239 if (flag & MOD_CAST_Z) 00240 tmp_co[2] = fac*vec[2]*len + facm*tmp_co[2]; 00241 00242 if(ctrl_ob) { 00243 if(flag & MOD_CAST_USE_OB_TRANSFORM) { 00244 mul_m4_v3(imat, tmp_co); 00245 } else { 00246 add_v3_v3(tmp_co, center); 00247 } 00248 } 00249 00250 copy_v3_v3(vertexCos[i], tmp_co); 00251 } 00252 return; 00253 } 00254 00255 /* no vgroup */ 00256 for (i = 0; i < numVerts; i++) { 00257 float tmp_co[3]; 00258 00259 copy_v3_v3(tmp_co, vertexCos[i]); 00260 if(ctrl_ob) { 00261 if(flag & MOD_CAST_USE_OB_TRANSFORM) { 00262 mul_m4_v3(mat, tmp_co); 00263 } else { 00264 sub_v3_v3(tmp_co, center); 00265 } 00266 } 00267 00268 copy_v3_v3(vec, tmp_co); 00269 00270 if (type == MOD_CAST_TYPE_CYLINDER) 00271 vec[2] = 0.0f; 00272 00273 if (has_radius) { 00274 if (len_v3(vec) > cmd->radius) continue; 00275 } 00276 00277 normalize_v3(vec); 00278 00279 if (flag & MOD_CAST_X) 00280 tmp_co[0] = fac*vec[0]*len + facm*tmp_co[0]; 00281 if (flag & MOD_CAST_Y) 00282 tmp_co[1] = fac*vec[1]*len + facm*tmp_co[1]; 00283 if (flag & MOD_CAST_Z) 00284 tmp_co[2] = fac*vec[2]*len + facm*tmp_co[2]; 00285 00286 if(ctrl_ob) { 00287 if(flag & MOD_CAST_USE_OB_TRANSFORM) { 00288 mul_m4_v3(imat, tmp_co); 00289 } else { 00290 add_v3_v3(tmp_co, center); 00291 } 00292 } 00293 00294 copy_v3_v3(vertexCos[i], tmp_co); 00295 } 00296 } 00297 00298 static void cuboid_do( 00299 CastModifierData *cmd, Object *ob, DerivedMesh *dm, 00300 float (*vertexCos)[3], int numVerts) 00301 { 00302 MDeformVert *dvert = NULL; 00303 Object *ctrl_ob = NULL; 00304 00305 int i, defgrp_index; 00306 int has_radius = 0; 00307 short flag; 00308 float fac, facm; 00309 float min[3], max[3], bb[8][3]; 00310 float center[3] = {0.0f, 0.0f, 0.0f}; 00311 float mat[4][4], imat[4][4]; 00312 00313 fac = cmd->fac; 00314 facm = 1.0f - fac; 00315 00316 flag = cmd->flag; 00317 00318 ctrl_ob = cmd->object; 00319 00320 /* now we check which options the user wants */ 00321 00322 /* 1) (flag was checked in the "if (ctrl_ob)" block above) */ 00323 /* 2) cmd->radius > 0.0f: only the vertices within this radius from 00324 * the center of the effect should be deformed */ 00325 if (cmd->radius > FLT_EPSILON) has_radius = 1; 00326 00327 /* 3) if we were given a vertex group name, 00328 * only those vertices should be affected */ 00329 modifier_get_vgroup(ob, dm, cmd->defgrp_name, &dvert, &defgrp_index); 00330 00331 if (ctrl_ob) { 00332 if(flag & MOD_CAST_USE_OB_TRANSFORM) { 00333 invert_m4_m4(ctrl_ob->imat, ctrl_ob->obmat); 00334 mult_m4_m4m4(mat, ctrl_ob->imat, ob->obmat); 00335 invert_m4_m4(imat, mat); 00336 } 00337 00338 invert_m4_m4(ob->imat, ob->obmat); 00339 mul_v3_m4v3(center, ob->imat, ctrl_ob->obmat[3]); 00340 } 00341 00342 if((flag & MOD_CAST_SIZE_FROM_RADIUS) && has_radius) { 00343 for(i = 0; i < 3; i++) { 00344 min[i] = -cmd->radius; 00345 max[i] = cmd->radius; 00346 } 00347 } else if(!(flag & MOD_CAST_SIZE_FROM_RADIUS) && cmd->size > 0) { 00348 for(i = 0; i < 3; i++) { 00349 min[i] = -cmd->size; 00350 max[i] = cmd->size; 00351 } 00352 } else { 00353 /* get bound box */ 00354 /* We can't use the object's bound box because other modifiers 00355 * may have changed the vertex data. */ 00356 INIT_MINMAX(min, max); 00357 00358 /* Cast's center is the ob's own center in its local space, 00359 * by default, but if the user defined a control object, we use 00360 * its location, transformed to ob's local space. */ 00361 if (ctrl_ob) { 00362 float vec[3]; 00363 00364 /* let the center of the ctrl_ob be part of the bound box: */ 00365 DO_MINMAX(center, min, max); 00366 00367 for (i = 0; i < numVerts; i++) { 00368 sub_v3_v3v3(vec, vertexCos[i], center); 00369 DO_MINMAX(vec, min, max); 00370 } 00371 } 00372 else { 00373 for (i = 0; i < numVerts; i++) { 00374 DO_MINMAX(vertexCos[i], min, max); 00375 } 00376 } 00377 00378 /* we want a symmetric bound box around the origin */ 00379 if (fabs(min[0]) > fabs(max[0])) max[0] = fabs(min[0]); 00380 if (fabs(min[1]) > fabs(max[1])) max[1] = fabs(min[1]); 00381 if (fabs(min[2]) > fabs(max[2])) max[2] = fabs(min[2]); 00382 min[0] = -max[0]; 00383 min[1] = -max[1]; 00384 min[2] = -max[2]; 00385 } 00386 00387 /* building our custom bounding box */ 00388 bb[0][0] = bb[2][0] = bb[4][0] = bb[6][0] = min[0]; 00389 bb[1][0] = bb[3][0] = bb[5][0] = bb[7][0] = max[0]; 00390 bb[0][1] = bb[1][1] = bb[4][1] = bb[5][1] = min[1]; 00391 bb[2][1] = bb[3][1] = bb[6][1] = bb[7][1] = max[1]; 00392 bb[0][2] = bb[1][2] = bb[2][2] = bb[3][2] = min[2]; 00393 bb[4][2] = bb[5][2] = bb[6][2] = bb[7][2] = max[2]; 00394 00395 /* ready to apply the effect, one vertex at a time; 00396 * tiny optimization: the code is separated (with parts repeated) 00397 * in two possible cases: 00398 * with or w/o a vgroup. With lots of if's in the code below, 00399 * further optimizations are possible, if needed */ 00400 if (dvert) { /* with a vgroup */ 00401 float fac_orig = fac; 00402 for (i = 0; i < numVerts; i++) { 00403 MDeformWeight *dw = NULL; 00404 int j, octant, coord; 00405 float d[3], dmax, apex[3], fbb; 00406 float tmp_co[3]; 00407 00408 copy_v3_v3(tmp_co, vertexCos[i]); 00409 if(ctrl_ob) { 00410 if(flag & MOD_CAST_USE_OB_TRANSFORM) { 00411 mul_m4_v3(mat, tmp_co); 00412 } else { 00413 sub_v3_v3(tmp_co, center); 00414 } 00415 } 00416 00417 if (has_radius) { 00418 if (fabsf(tmp_co[0]) > cmd->radius || 00419 fabsf(tmp_co[1]) > cmd->radius || 00420 fabsf(tmp_co[2]) > cmd->radius) continue; 00421 } 00422 00423 for (j = 0; j < dvert[i].totweight; ++j) { 00424 if(dvert[i].dw[j].def_nr == defgrp_index) { 00425 dw = &dvert[i].dw[j]; 00426 break; 00427 } 00428 } 00429 if (!dw) continue; 00430 00431 fac = fac_orig * dw->weight; 00432 facm = 1.0f - fac; 00433 00434 /* The algo used to project the vertices to their 00435 * bounding box (bb) is pretty simple: 00436 * for each vertex v: 00437 * 1) find in which octant v is in; 00438 * 2) find which outer "wall" of that octant is closer to v; 00439 * 3) calculate factor (var fbb) to project v to that wall; 00440 * 4) project. */ 00441 00442 /* find in which octant this vertex is in */ 00443 octant = 0; 00444 if (tmp_co[0] > 0.0f) octant += 1; 00445 if (tmp_co[1] > 0.0f) octant += 2; 00446 if (tmp_co[2] > 0.0f) octant += 4; 00447 00448 /* apex is the bb's vertex at the chosen octant */ 00449 copy_v3_v3(apex, bb[octant]); 00450 00451 /* find which bb plane is closest to this vertex ... */ 00452 d[0] = tmp_co[0] / apex[0]; 00453 d[1] = tmp_co[1] / apex[1]; 00454 d[2] = tmp_co[2] / apex[2]; 00455 00456 /* ... (the closest has the higher (closer to 1) d value) */ 00457 dmax = d[0]; 00458 coord = 0; 00459 if (d[1] > dmax) { 00460 dmax = d[1]; 00461 coord = 1; 00462 } 00463 if (d[2] > dmax) { 00464 /* dmax = d[2]; */ /* commented, we don't need it */ 00465 coord = 2; 00466 } 00467 00468 /* ok, now we know which coordinate of the vertex to use */ 00469 00470 if (fabsf(tmp_co[coord]) < FLT_EPSILON) /* avoid division by zero */ 00471 continue; 00472 00473 /* finally, this is the factor we wanted, to project the vertex 00474 * to its bounding box (bb) */ 00475 fbb = apex[coord] / tmp_co[coord]; 00476 00477 /* calculate the new vertex position */ 00478 if (flag & MOD_CAST_X) 00479 tmp_co[0] = facm * tmp_co[0] + fac * tmp_co[0] * fbb; 00480 if (flag & MOD_CAST_Y) 00481 tmp_co[1] = facm * tmp_co[1] + fac * tmp_co[1] * fbb; 00482 if (flag & MOD_CAST_Z) 00483 tmp_co[2] = facm * tmp_co[2] + fac * tmp_co[2] * fbb; 00484 00485 if(ctrl_ob) { 00486 if(flag & MOD_CAST_USE_OB_TRANSFORM) { 00487 mul_m4_v3(imat, tmp_co); 00488 } else { 00489 add_v3_v3(tmp_co, center); 00490 } 00491 } 00492 00493 copy_v3_v3(vertexCos[i], tmp_co); 00494 } 00495 return; 00496 } 00497 00498 /* no vgroup (check previous case for comments about the code) */ 00499 for (i = 0; i < numVerts; i++) { 00500 int octant, coord; 00501 float d[3], dmax, fbb, apex[3]; 00502 float tmp_co[3]; 00503 00504 copy_v3_v3(tmp_co, vertexCos[i]); 00505 if(ctrl_ob) { 00506 if(flag & MOD_CAST_USE_OB_TRANSFORM) { 00507 mul_m4_v3(mat, tmp_co); 00508 } else { 00509 sub_v3_v3(tmp_co, center); 00510 } 00511 } 00512 00513 if (has_radius) { 00514 if (fabsf(tmp_co[0]) > cmd->radius || 00515 fabsf(tmp_co[1]) > cmd->radius || 00516 fabsf(tmp_co[2]) > cmd->radius) continue; 00517 } 00518 00519 octant = 0; 00520 if (tmp_co[0] > 0.0f) octant += 1; 00521 if (tmp_co[1] > 0.0f) octant += 2; 00522 if (tmp_co[2] > 0.0f) octant += 4; 00523 00524 copy_v3_v3(apex, bb[octant]); 00525 00526 d[0] = tmp_co[0] / apex[0]; 00527 d[1] = tmp_co[1] / apex[1]; 00528 d[2] = tmp_co[2] / apex[2]; 00529 00530 dmax = d[0]; 00531 coord = 0; 00532 if (d[1] > dmax) { 00533 dmax = d[1]; 00534 coord = 1; 00535 } 00536 if (d[2] > dmax) { 00537 /* dmax = d[2]; */ /* commented, we don't need it */ 00538 coord = 2; 00539 } 00540 00541 if (fabsf(tmp_co[coord]) < FLT_EPSILON) 00542 continue; 00543 00544 fbb = apex[coord] / tmp_co[coord]; 00545 00546 if (flag & MOD_CAST_X) 00547 tmp_co[0] = facm * tmp_co[0] + fac * tmp_co[0] * fbb; 00548 if (flag & MOD_CAST_Y) 00549 tmp_co[1] = facm * tmp_co[1] + fac * tmp_co[1] * fbb; 00550 if (flag & MOD_CAST_Z) 00551 tmp_co[2] = facm * tmp_co[2] + fac * tmp_co[2] * fbb; 00552 00553 if(ctrl_ob) { 00554 if(flag & MOD_CAST_USE_OB_TRANSFORM) { 00555 mul_m4_v3(imat, tmp_co); 00556 } else { 00557 add_v3_v3(tmp_co, center); 00558 } 00559 } 00560 00561 copy_v3_v3(vertexCos[i], tmp_co); 00562 } 00563 } 00564 00565 static void deformVerts(ModifierData *md, Object *ob, 00566 DerivedMesh *derivedData, 00567 float (*vertexCos)[3], 00568 int numVerts, 00569 int UNUSED(useRenderParams), 00570 int UNUSED(isFinalCalc)) 00571 { 00572 DerivedMesh *dm = NULL; 00573 CastModifierData *cmd = (CastModifierData *)md; 00574 00575 dm = get_dm(ob, NULL, derivedData, NULL, 0); 00576 00577 if (cmd->type == MOD_CAST_TYPE_CUBOID) { 00578 cuboid_do(cmd, ob, dm, vertexCos, numVerts); 00579 } else { /* MOD_CAST_TYPE_SPHERE or MOD_CAST_TYPE_CYLINDER */ 00580 sphere_do(cmd, ob, dm, vertexCos, numVerts); 00581 } 00582 00583 if(dm != derivedData) 00584 dm->release(dm); 00585 } 00586 00587 static void deformVertsEM( 00588 ModifierData *md, Object *ob, struct EditMesh *editData, 00589 DerivedMesh *derivedData, float (*vertexCos)[3], int numVerts) 00590 { 00591 DerivedMesh *dm = get_dm(ob, editData, derivedData, NULL, 0); 00592 CastModifierData *cmd = (CastModifierData *)md; 00593 00594 if (cmd->type == MOD_CAST_TYPE_CUBOID) { 00595 cuboid_do(cmd, ob, dm, vertexCos, numVerts); 00596 } else { /* MOD_CAST_TYPE_SPHERE or MOD_CAST_TYPE_CYLINDER */ 00597 sphere_do(cmd, ob, dm, vertexCos, numVerts); 00598 } 00599 00600 if(dm != derivedData) 00601 dm->release(dm); 00602 } 00603 00604 00605 ModifierTypeInfo modifierType_Cast = { 00606 /* name */ "Cast", 00607 /* structName */ "CastModifierData", 00608 /* structSize */ sizeof(CastModifierData), 00609 /* type */ eModifierTypeType_OnlyDeform, 00610 /* flags */ eModifierTypeFlag_AcceptsCVs 00611 | eModifierTypeFlag_SupportsEditmode, 00612 00613 /* copyData */ copyData, 00614 /* deformVerts */ deformVerts, 00615 /* deformMatrices */ NULL, 00616 /* deformVertsEM */ deformVertsEM, 00617 /* deformMatricesEM */ NULL, 00618 /* applyModifier */ NULL, 00619 /* applyModifierEM */ NULL, 00620 /* initData */ initData, 00621 /* requiredDataMask */ requiredDataMask, 00622 /* freeData */ NULL, 00623 /* isDisabled */ isDisabled, 00624 /* updateDepgraph */ updateDepgraph, 00625 /* dependsOnTime */ NULL, 00626 /* dependsOnNormals */ NULL, 00627 /* foreachObjectLink */ foreachObjectLink, 00628 /* foreachIDLink */ NULL, 00629 /* foreachTexLink */ NULL, 00630 };