OpenSubdiv: Initial work to support UV maps in textured OSD viewport
[blender.git] / source / blender / blenkernel / intern / CCGSubSurf_opensubdiv_converter.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  * ***** END GPL LICENSE BLOCK *****
19  */
20
21 /** \file blender/blenkernel/intern/CCGSubSurf_opensubdiv_converter.c
22  *  \ingroup bke
23  */
24
25 #ifdef WITH_OPENSUBDIV
26
27 #include <stdlib.h>
28
29 #include "MEM_guardedalloc.h"
30 #include "BLI_sys_types.h" // for intptr_t support
31
32 #include "BLI_utildefines.h" /* for BLI_assert */
33 #include "BLI_math.h"
34
35 #include "CCGSubSurf.h"
36 #include "CCGSubSurf_intern.h"
37
38 #include "BKE_DerivedMesh.h"
39 #include "BKE_mesh_mapping.h"
40
41 #include "opensubdiv_capi.h"
42 #include "opensubdiv_converter_capi.h"
43
44 /* Use mesh element mapping structures during conversion.
45  * Uses more memory but is much faster than naive algorithm.
46  */
47 #define USE_MESH_ELEMENT_MAPPING
48
49 /**
50  * Converter from DerivedMesh.
51  */
52
53 typedef struct ConvDMStorage {
54         CCGSubSurf *ss;
55         DerivedMesh *dm;
56
57 #ifdef USE_MESH_ELEMENT_MAPPING
58         MeshElemMap *vert_edge_map,
59                     *vert_poly_map,
60                     *edge_poly_map;
61         int *vert_edge_mem,
62             *vert_poly_mem,
63             *edge_poly_mem;
64 #endif
65
66         MEdge *medge;
67         MLoop *mloop;
68         MPoly *mpoly;
69         MLoopUV *mloopuv;
70 } ConvDMStorage;
71
72 static OpenSubdiv_SchemeType conv_dm_get_type(
73         const OpenSubdiv_Converter *converter)
74 {
75         ConvDMStorage *storage = converter->user_data;
76         if (storage->ss->meshIFC.simpleSubdiv)
77                 return OSD_SCHEME_BILINEAR;
78         else
79                 return OSD_SCHEME_CATMARK;
80 }
81
82 static int conv_dm_get_num_faces(const OpenSubdiv_Converter *converter)
83 {
84         ConvDMStorage *storage = converter->user_data;
85         DerivedMesh *dm = storage->dm;
86         return dm->getNumPolys(dm);
87 }
88
89 static int conv_dm_get_num_edges(const OpenSubdiv_Converter *converter)
90 {
91         ConvDMStorage *storage = converter->user_data;
92         DerivedMesh *dm = storage->dm;
93         return dm->getNumEdges(dm);
94 }
95
96 static int conv_dm_get_num_verts(const OpenSubdiv_Converter *converter)
97 {
98         ConvDMStorage *storage = converter->user_data;
99         DerivedMesh *dm = storage->dm;
100         return dm->getNumVerts(dm);
101 }
102
103 static int conv_dm_get_num_face_verts(const OpenSubdiv_Converter *converter,
104                                       int face)
105 {
106         ConvDMStorage *storage = converter->user_data;
107         const MPoly *mpoly = &storage->mpoly[face];
108         return mpoly->totloop;
109 }
110
111 static void conv_dm_get_face_verts(const OpenSubdiv_Converter *converter,
112                                    int face,
113                                    int *face_verts)
114 {
115         ConvDMStorage *storage = converter->user_data;
116         const MPoly *mpoly = &storage->mpoly[face];
117         int loop;
118         for (loop = 0; loop < mpoly->totloop; loop++) {
119                 face_verts[loop] = storage->mloop[mpoly->loopstart + loop].v;
120         }
121 }
122
123 static void conv_dm_get_face_edges(const OpenSubdiv_Converter *converter,
124                                    int face,
125                                    int *face_edges)
126 {
127         ConvDMStorage *storage = converter->user_data;
128         const MPoly *mpoly = &storage->mpoly[face];
129         int loop;
130         for (loop = 0; loop < mpoly->totloop; loop++) {
131                 face_edges[loop] = storage->mloop[mpoly->loopstart + loop].e;
132         }
133 }
134
135 static void conv_dm_get_edge_verts(const OpenSubdiv_Converter *converter,
136                                    int edge,
137                                    int *edge_verts)
138 {
139         ConvDMStorage *storage = converter->user_data;
140         const MEdge *medge = &storage->medge[edge];
141         edge_verts[0] = medge->v1;
142         edge_verts[1] = medge->v2;
143 }
144
145 static int conv_dm_get_num_edge_faces(const OpenSubdiv_Converter *converter,
146                                       int edge)
147 {
148         ConvDMStorage *storage = converter->user_data;
149 #ifndef USE_MESH_ELEMENT_MAPPING
150         DerivedMesh *dm = storage->dm;
151         int num = 0, poly;
152         for (poly = 0; poly < dm->getNumPolys(dm); poly++) {
153                 const MPoly *mpoly = &user_data->mpoly[poly];
154                 int loop;
155                 for (loop = 0; loop < mpoly->totloop; loop++) {
156                         const MLoop *mloop = &storage->mloop[mpoly->loopstart + loop];
157                         if (mloop->e == edge) {
158                                 ++num;
159                                 break;
160                         }
161                 }
162         }
163         return num;
164 #else
165         return storage->edge_poly_map[edge].count;
166 #endif
167 }
168
169 static void conv_dm_get_edge_faces(const OpenSubdiv_Converter *converter,
170                                    int edge,
171                                    int *edge_faces)
172 {
173         ConvDMStorage *storage = converter->user_data;
174 #ifndef USE_MESH_ELEMENT_MAPPING
175         DerivedMesh *dm = storage->dm;
176         int num = 0, poly;
177         for (poly = 0; poly < dm->getNumPolys(dm); poly++) {
178                 const MPoly *mpoly = &user_data->mpoly[poly];
179                 int loop;
180                 for (loop = 0; loop < mpoly->totloop; loop++) {
181                         const MLoop *mloop = &storage->mloop[mpoly->loopstart + loop];
182                         if (mloop->e == edge) {
183                                 edge_faces[num++] = poly;
184                                 break;
185                         }
186                 }
187         }
188 #else
189         memcpy(edge_faces,
190                storage->edge_poly_map[edge].indices,
191                sizeof(int) * storage->edge_poly_map[edge].count);
192 #endif
193 }
194
195 static float conv_dm_get_edge_sharpness(const OpenSubdiv_Converter *converter,
196                                         int edge)
197 {
198         ConvDMStorage *storage = converter->user_data;
199         CCGSubSurf *ss = storage->ss;
200         const MEdge *medge = storage->medge;
201         return (float)medge[edge].crease / 255.0f * ss->subdivLevels;
202 }
203
204 static int conv_dm_get_num_vert_edges(const OpenSubdiv_Converter *converter,
205                                       int vert)
206 {
207         ConvDMStorage *storage = converter->user_data;
208 #ifndef USE_MESH_ELEMENT_MAPPING
209         DerivedMesh *dm = storage->dm;
210         int num = 0, edge;
211         for (edge = 0; edge < dm->getNumEdges(dm); edge++) {
212                 const MEdge *medge = &user_data->medge[edge];
213                 if (medge->v1 == vert || medge->v2 == vert) {
214                         ++num;
215                 }
216         }
217         return num;
218 #else
219         return storage->vert_edge_map[vert].count;
220 #endif
221 }
222
223 static void conv_dm_get_vert_edges(const OpenSubdiv_Converter *converter,
224                                    int vert,
225                                    int *vert_edges)
226 {
227         ConvDMStorage *storage = converter->user_data;
228 #ifndef USE_MESH_ELEMENT_MAPPING
229         DerivedMesh *dm = storage->dm;
230         int num = 0, edge;
231         for (edge = 0; edge < dm->getNumEdges(dm); edge++) {
232                 const MEdge *medge = &user_data->medge[edge];
233                 if (medge->v1 == vert || medge->v2 == vert) {
234                         vert_edges[num++] = edge;
235                 }
236         }
237 #else
238         memcpy(vert_edges,
239                storage->vert_edge_map[vert].indices,
240                sizeof(int) * storage->vert_edge_map[vert].count);
241 #endif
242 }
243
244 static int conv_dm_get_num_vert_faces(const OpenSubdiv_Converter *converter,
245                                       int vert)
246 {
247         ConvDMStorage *storage = converter->user_data;
248 #ifndef USE_MESH_ELEMENT_MAPPING
249         DerivedMesh *dm = storage->dm;
250         int num = 0, poly;
251         for (poly = 0; poly < dm->getNumPolys(dm); poly++) {
252                 const MPoly *mpoly = &user_data->mpoly[poly];
253                 int loop;
254                 for (loop = 0; loop < mpoly->totloop; loop++) {
255                         const MLoop *mloop = &storage->mloop[mpoly->loopstart + loop];
256                         if (mloop->v == vert) {
257                                 ++num;
258                                 break;
259                         }
260                 }
261         }
262         return num;
263 #else
264         return storage->vert_poly_map[vert].count;
265 #endif
266 }
267
268 static void conv_dm_get_vert_faces(const OpenSubdiv_Converter *converter,
269                                    int vert,
270                                    int *vert_faces)
271 {
272         ConvDMStorage *storage = converter->user_data;
273 #ifndef USE_MESH_ELEMENT_MAPPING
274         DerivedMesh *dm = storage->dm;
275         int num = 0, poly;
276         for (poly = 0; poly < dm->getNumPolys(dm); poly++) {
277                 const MPoly *mpoly = &storage->mpoly[poly];
278                 int loop;
279                 for (loop = 0; loop < mpoly->totloop; loop++) {
280                         const MLoop *mloop = &storage->mloop[mpoly->loopstart + loop];
281                         if (mloop->v == vert) {
282                                 vert_faces[num++] = poly;
283                                 break;
284                         }
285                 }
286         }
287 #else
288         memcpy(vert_faces,
289                storage->vert_poly_map[vert].indices,
290                sizeof(int) * storage->vert_poly_map[vert].count);
291 #endif
292 }
293
294 static int conv_dm_get_num_uv_layers(const OpenSubdiv_Converter *converter)
295 {
296         ConvDMStorage *storage = converter->user_data;
297         DerivedMesh *dm = storage->dm;
298         return CustomData_number_of_layers(&dm->loopData, CD_MLOOPUV);
299 }
300
301 static void conv_dm_get_face_corner_uv(const OpenSubdiv_Converter *converter,
302                                        int face,
303                                        int corner,
304                                        float r_uv[2])
305 {
306         ConvDMStorage *storage = converter->user_data;
307         MPoly *mpoly = &storage->mpoly[face];
308         MLoopUV *mloopuv = &storage->mloopuv[mpoly->loopstart + corner];
309         copy_v2_v2(r_uv, mloopuv->uv);
310 }
311
312 static void conv_dm_free_user_data(const OpenSubdiv_Converter *converter)
313 {
314         ConvDMStorage *user_data = converter->user_data;
315 #ifdef USE_MESH_ELEMENT_MAPPING
316         MEM_freeN(user_data->vert_edge_map);
317         MEM_freeN(user_data->vert_edge_mem);
318         MEM_freeN(user_data->vert_poly_map);
319         MEM_freeN(user_data->vert_poly_mem);
320         MEM_freeN(user_data->edge_poly_map);
321         MEM_freeN(user_data->edge_poly_mem);
322 #endif
323         MEM_freeN(user_data);
324 }
325
326 void ccgSubSurf_converter_setup_from_derivedmesh(
327         CCGSubSurf *ss,
328         DerivedMesh *dm,
329         OpenSubdiv_Converter *converter)
330 {
331         ConvDMStorage *user_data;
332
333         converter->get_type = conv_dm_get_type;
334
335         converter->get_num_faces = conv_dm_get_num_faces;
336         converter->get_num_edges = conv_dm_get_num_edges;
337         converter->get_num_verts = conv_dm_get_num_verts;
338
339         converter->get_num_face_verts = conv_dm_get_num_face_verts;
340         converter->get_face_verts = conv_dm_get_face_verts;
341         converter->get_face_edges = conv_dm_get_face_edges;
342
343         converter->get_edge_verts = conv_dm_get_edge_verts;
344         converter->get_num_edge_faces = conv_dm_get_num_edge_faces;
345         converter->get_edge_faces = conv_dm_get_edge_faces;
346         converter->get_edge_sharpness = conv_dm_get_edge_sharpness;
347
348         converter->get_num_vert_edges = conv_dm_get_num_vert_edges;
349         converter->get_vert_edges = conv_dm_get_vert_edges;
350         converter->get_num_vert_faces = conv_dm_get_num_vert_faces;
351         converter->get_vert_faces = conv_dm_get_vert_faces;
352
353         converter->get_num_uv_layers = conv_dm_get_num_uv_layers;
354         converter->get_face_corner_uv = conv_dm_get_face_corner_uv;
355
356         user_data = MEM_mallocN(sizeof(ConvDMStorage), __func__);
357         user_data->ss = ss;
358         user_data->dm = dm;
359
360         user_data->medge = dm->getEdgeArray(dm);
361         user_data->mloop = dm->getLoopArray(dm);
362         user_data->mpoly = dm->getPolyArray(dm);
363         user_data->mloopuv = DM_get_loop_data_layer(dm, CD_MLOOPUV);
364
365         converter->free_user_data = conv_dm_free_user_data;
366         converter->user_data = user_data;
367
368 #ifdef USE_MESH_ELEMENT_MAPPING
369         {
370                 const MEdge *medge = dm->getEdgeArray(dm);
371                 const MLoop *mloop = dm->getLoopArray(dm);
372                 const MPoly *mpoly = dm->getPolyArray(dm);
373                 const int num_vert = dm->getNumVerts(dm),
374                           num_edge = dm->getNumEdges(dm),
375                           num_loop = dm->getNumLoops(dm),
376                           num_poly = dm->getNumPolys(dm);
377                 BKE_mesh_vert_edge_map_create(&user_data->vert_edge_map,
378                                               &user_data->vert_edge_mem,
379                                               medge,
380                                               num_vert,
381                                               num_edge);
382
383                 BKE_mesh_vert_poly_map_create(&user_data->vert_poly_map,
384                                               &user_data->vert_poly_mem,
385                                               mpoly,
386                                               mloop,
387                                               num_vert,
388                                               num_poly,
389                                               num_loop);
390
391                 BKE_mesh_edge_poly_map_create(&user_data->edge_poly_map,
392                                               &user_data->edge_poly_mem,
393                                               medge,
394                                               num_edge,
395                                               mpoly,
396                                               num_poly,
397                                               mloop,
398                                               num_loop);
399         }
400 #endif  /* USE_MESH_ELEMENT_MAPPING */
401 }
402
403 /**
404  * Converter from CCGSubSurf
405  */
406
407 static OpenSubdiv_SchemeType conv_ccg_get_bilinear_type(
408         const OpenSubdiv_Converter *converter)
409 {
410         CCGSubSurf *ss = converter->user_data;
411         if (ss->meshIFC.simpleSubdiv) {
412                 return OSD_SCHEME_BILINEAR;
413         }
414         else {
415                 return OSD_SCHEME_CATMARK;
416         }
417 }
418
419 static int conv_ccg_get_num_faces(const OpenSubdiv_Converter *converter)
420 {
421         CCGSubSurf *ss = converter->user_data;
422         return ss->fMap->numEntries;
423 }
424
425 static int conv_ccg_get_num_edges(const OpenSubdiv_Converter *converter)
426 {
427         CCGSubSurf *ss = converter->user_data;
428         return ss->eMap->numEntries;
429 }
430
431 static int conv_ccg_get_num_verts(const OpenSubdiv_Converter *converter)
432 {
433         CCGSubSurf *ss = converter->user_data;
434         return ss->vMap->numEntries;
435 }
436
437 static int conv_ccg_get_num_face_verts(const OpenSubdiv_Converter *converter,
438                                        int face)
439 {
440         CCGSubSurf *ss = converter->user_data;
441         CCGFace *ccg_face = ccgSubSurf_getFace(ss, SET_INT_IN_POINTER(face));
442         return ccgSubSurf_getFaceNumVerts(ccg_face);
443 }
444
445 static void conv_ccg_get_face_verts(const OpenSubdiv_Converter *converter,
446                                     int face,
447                                     int *face_verts)
448 {
449         CCGSubSurf *ss = converter->user_data;
450         CCGFace *ccg_face = ccgSubSurf_getFace(ss, SET_INT_IN_POINTER(face));
451         int num_face_verts = ccgSubSurf_getFaceNumVerts(ccg_face);
452         int loop;
453         for (loop = 0; loop < num_face_verts; loop++) {
454                 CCGVert *ccg_vert = ccgSubSurf_getFaceVert(ccg_face, loop);
455                 face_verts[loop] = GET_INT_FROM_POINTER(ccgSubSurf_getVertVertHandle(ccg_vert));
456         }
457 }
458
459 static void conv_ccg_get_face_edges(const OpenSubdiv_Converter *converter,
460                                     int face,
461                                     int *face_edges)
462 {
463         CCGSubSurf *ss = converter->user_data;
464         CCGFace *ccg_face = ccgSubSurf_getFace(ss, SET_INT_IN_POINTER(face));
465         int num_face_verts = ccgSubSurf_getFaceNumVerts(ccg_face);
466         int loop;
467         for (loop = 0; loop < num_face_verts; loop++) {
468                 CCGEdge *ccg_edge = ccgSubSurf_getFaceEdge(ccg_face, loop);
469                 face_edges[loop] = GET_INT_FROM_POINTER(ccgSubSurf_getEdgeEdgeHandle(ccg_edge));
470         }
471 }
472
473 static void conv_ccg_get_edge_verts(const OpenSubdiv_Converter *converter,
474                                     int edge,
475                                     int *edge_verts)
476 {
477         CCGSubSurf *ss = converter->user_data;
478         CCGEdge *ccg_edge = ccgSubSurf_getEdge(ss, SET_INT_IN_POINTER(edge));
479         CCGVert *ccg_vert0 = ccgSubSurf_getEdgeVert0(ccg_edge);
480         CCGVert *ccg_vert1 = ccgSubSurf_getEdgeVert1(ccg_edge);
481         edge_verts[0] = GET_INT_FROM_POINTER(ccgSubSurf_getVertVertHandle(ccg_vert0));
482         edge_verts[1] = GET_INT_FROM_POINTER(ccgSubSurf_getVertVertHandle(ccg_vert1));
483 }
484
485 static int conv_ccg_get_num_edge_faces(const OpenSubdiv_Converter *converter,
486                                        int edge)
487 {
488         CCGSubSurf *ss = converter->user_data;
489         CCGEdge *ccg_edge = ccgSubSurf_getEdge(ss, SET_INT_IN_POINTER(edge));
490         return ccgSubSurf_getEdgeNumFaces(ccg_edge);
491 }
492
493 static void conv_ccg_get_edge_faces(const OpenSubdiv_Converter *converter,
494                                     int edge,
495                                     int *edge_faces)
496 {
497         CCGSubSurf *ss = converter->user_data;
498         CCGEdge *ccg_edge = ccgSubSurf_getEdge(ss, SET_INT_IN_POINTER(edge));
499         int num_edge_faces = ccgSubSurf_getEdgeNumFaces(ccg_edge);
500         int face;
501         for (face = 0; face < num_edge_faces; face++) {
502                 CCGFace *ccg_face = ccgSubSurf_getEdgeFace(ccg_edge, face);
503                 edge_faces[face] = GET_INT_FROM_POINTER(ccgSubSurf_getFaceFaceHandle(ccg_face));
504         }
505 }
506
507 static float conv_ccg_get_edge_sharpness(const OpenSubdiv_Converter *converter,
508                                          int edge)
509 {
510         CCGSubSurf *ss = converter->user_data;
511         CCGEdge *ccg_edge = ccgSubSurf_getEdge(ss, SET_INT_IN_POINTER(edge));
512         /* TODO(sergey): Multiply by subdivision level once CPU evaluator
513          * is switched to uniform subdivision type.
514          */
515         return ccg_edge->crease;
516 }
517
518 static int conv_ccg_get_num_vert_edges(const OpenSubdiv_Converter *converter,
519                                        int vert)
520 {
521         CCGSubSurf *ss = converter->user_data;
522         CCGVert *ccg_vert = ccgSubSurf_getVert(ss, SET_INT_IN_POINTER(vert));
523         return ccgSubSurf_getVertNumEdges(ccg_vert);
524 }
525
526 static void conv_ccg_get_vert_edges(const OpenSubdiv_Converter *converter,
527                                     int vert,
528                                     int *vert_edges)
529 {
530         CCGSubSurf *ss = converter->user_data;
531         CCGVert *ccg_vert = ccgSubSurf_getVert(ss, SET_INT_IN_POINTER(vert));
532         int num_vert_edges = ccgSubSurf_getVertNumEdges(ccg_vert);
533         int edge;
534         for (edge = 0; edge < num_vert_edges; edge++) {
535                 CCGEdge *ccg_edge = ccgSubSurf_getVertEdge(ccg_vert, edge);
536                 vert_edges[edge] = GET_INT_FROM_POINTER(ccgSubSurf_getEdgeEdgeHandle(ccg_edge));
537         }
538 }
539
540 static int conv_ccg_get_num_vert_faces(const OpenSubdiv_Converter *converter,
541                                        int vert)
542 {
543         CCGSubSurf *ss = converter->user_data;
544         CCGVert *ccg_vert = ccgSubSurf_getVert(ss, SET_INT_IN_POINTER(vert));
545         return ccgSubSurf_getVertNumFaces(ccg_vert);
546 }
547
548 static void conv_ccg_get_vert_faces(const OpenSubdiv_Converter *converter,
549                                     int vert,
550                                     int *vert_faces)
551 {
552         CCGSubSurf *ss = converter->user_data;
553         CCGVert *ccg_vert = ccgSubSurf_getVert(ss, SET_INT_IN_POINTER(vert));
554         int num_vert_faces = ccgSubSurf_getVertNumFaces(ccg_vert);
555         int face;
556         for (face = 0; face < num_vert_faces; face++) {
557                 CCGFace *ccg_face = ccgSubSurf_getVertFace(ccg_vert, face);
558                 vert_faces[face] = GET_INT_FROM_POINTER(ccgSubSurf_getFaceFaceHandle(ccg_face));
559         }
560 }
561
562 static int conv_ccg_get_num_uv_layers(const OpenSubdiv_Converter *UNUSED(converter))
563 {
564         return 0;
565 }
566
567 static void conv_ccg_get_face_corner_uv(const OpenSubdiv_Converter * UNUSED(converter),
568                                         int UNUSED(face),
569                                         int UNUSED(corner),
570                                         float r_uv[2])
571 {
572         zero_v2(r_uv);
573 }
574
575 void ccgSubSurf_converter_setup_from_ccg(CCGSubSurf *ss,
576                                          OpenSubdiv_Converter *converter)
577 {
578         converter->get_type = conv_ccg_get_bilinear_type;
579
580         converter->get_num_faces = conv_ccg_get_num_faces;
581         converter->get_num_edges = conv_ccg_get_num_edges;
582         converter->get_num_verts = conv_ccg_get_num_verts;
583
584         converter->get_num_face_verts = conv_ccg_get_num_face_verts;
585         converter->get_face_verts = conv_ccg_get_face_verts;
586         converter->get_face_edges = conv_ccg_get_face_edges;
587
588         converter->get_edge_verts = conv_ccg_get_edge_verts;
589         converter->get_num_edge_faces = conv_ccg_get_num_edge_faces;
590         converter->get_edge_faces = conv_ccg_get_edge_faces;
591         converter->get_edge_sharpness = conv_ccg_get_edge_sharpness;
592
593         converter->get_num_vert_edges = conv_ccg_get_num_vert_edges;
594         converter->get_vert_edges = conv_ccg_get_vert_edges;
595         converter->get_num_vert_faces = conv_ccg_get_num_vert_faces;
596         converter->get_vert_faces = conv_ccg_get_vert_faces;
597
598         converter->get_num_uv_layers = conv_ccg_get_num_uv_layers;
599         converter->get_face_corner_uv = conv_ccg_get_face_corner_uv;
600
601         converter->free_user_data = NULL;
602         converter->user_data = ss;
603 }
604
605 void ccgSubSurf_converter_free(
606         struct OpenSubdiv_Converter *converter)
607 {
608         if (converter->free_user_data) {
609                 converter->free_user_data(converter);
610         }
611 }
612
613 #endif  /* WITH_OPENSUBDIV */