1 /*
2 * Copyright 2011-2013 Blender Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "bvh/bvh.h"
18 #include "bvh/bvh_build.h"
19
20 #include "device/device.h"
21
22 #include "render/graph.h"
23 #include "render/hair.h"
24 #include "render/mesh.h"
25 #include "render/object.h"
26 #include "render/scene.h"
27
28 #include "subd/subd_patch_table.h"
29 #include "subd/subd_split.h"
30
31 #include "util/util_foreach.h"
32 #include "util/util_logging.h"
33 #include "util/util_progress.h"
34 #include "util/util_set.h"
35
36 CCL_NAMESPACE_BEGIN
37
38 /* Triangle */
39
bounds_grow(const float3 * verts,BoundBox & bounds) const40 void Mesh::Triangle::bounds_grow(const float3 *verts, BoundBox &bounds) const
41 {
42 bounds.grow(verts[v[0]]);
43 bounds.grow(verts[v[1]]);
44 bounds.grow(verts[v[2]]);
45 }
46
motion_verts(const float3 * verts,const float3 * vert_steps,size_t num_verts,size_t num_steps,float time,float3 r_verts[3]) const47 void Mesh::Triangle::motion_verts(const float3 *verts,
48 const float3 *vert_steps,
49 size_t num_verts,
50 size_t num_steps,
51 float time,
52 float3 r_verts[3]) const
53 {
54 /* Figure out which steps we need to fetch and their interpolation factor. */
55 const size_t max_step = num_steps - 1;
56 const size_t step = min((int)(time * max_step), max_step - 1);
57 const float t = time * max_step - step;
58 /* Fetch vertex coordinates. */
59 float3 curr_verts[3];
60 float3 next_verts[3];
61 verts_for_step(verts, vert_steps, num_verts, num_steps, step, curr_verts);
62 verts_for_step(verts, vert_steps, num_verts, num_steps, step + 1, next_verts);
63 /* Interpolate between steps. */
64 r_verts[0] = (1.0f - t) * curr_verts[0] + t * next_verts[0];
65 r_verts[1] = (1.0f - t) * curr_verts[1] + t * next_verts[1];
66 r_verts[2] = (1.0f - t) * curr_verts[2] + t * next_verts[2];
67 }
68
verts_for_step(const float3 * verts,const float3 * vert_steps,size_t num_verts,size_t num_steps,size_t step,float3 r_verts[3]) const69 void Mesh::Triangle::verts_for_step(const float3 *verts,
70 const float3 *vert_steps,
71 size_t num_verts,
72 size_t num_steps,
73 size_t step,
74 float3 r_verts[3]) const
75 {
76 const size_t center_step = ((num_steps - 1) / 2);
77 if (step == center_step) {
78 /* Center step: regular vertex location. */
79 r_verts[0] = verts[v[0]];
80 r_verts[1] = verts[v[1]];
81 r_verts[2] = verts[v[2]];
82 }
83 else {
84 /* Center step not stored in the attribute array array. */
85 if (step > center_step) {
86 step--;
87 }
88 size_t offset = step * num_verts;
89 r_verts[0] = vert_steps[offset + v[0]];
90 r_verts[1] = vert_steps[offset + v[1]];
91 r_verts[2] = vert_steps[offset + v[2]];
92 }
93 }
94
compute_normal(const float3 * verts) const95 float3 Mesh::Triangle::compute_normal(const float3 *verts) const
96 {
97 const float3 &v0 = verts[v[0]];
98 const float3 &v1 = verts[v[1]];
99 const float3 &v2 = verts[v[2]];
100 const float3 norm = cross(v1 - v0, v2 - v0);
101 const float normlen = len(norm);
102 if (normlen == 0.0f) {
103 return make_float3(1.0f, 0.0f, 0.0f);
104 }
105 return norm / normlen;
106 }
107
valid(const float3 * verts) const108 bool Mesh::Triangle::valid(const float3 *verts) const
109 {
110 return isfinite3_safe(verts[v[0]]) && isfinite3_safe(verts[v[1]]) && isfinite3_safe(verts[v[2]]);
111 }
112
113 /* SubdFace */
114
normal(const Mesh * mesh) const115 float3 Mesh::SubdFace::normal(const Mesh *mesh) const
116 {
117 float3 v0 = mesh->verts[mesh->subd_face_corners[start_corner + 0]];
118 float3 v1 = mesh->verts[mesh->subd_face_corners[start_corner + 1]];
119 float3 v2 = mesh->verts[mesh->subd_face_corners[start_corner + 2]];
120
121 return safe_normalize(cross(v1 - v0, v2 - v0));
122 }
123
124 /* Mesh */
125
NODE_DEFINE(Mesh)126 NODE_DEFINE(Mesh)
127 {
128 NodeType *type = NodeType::add("mesh", create, NodeType::NONE, Geometry::node_base_type);
129
130 SOCKET_INT_ARRAY(triangles, "Triangles", array<int>());
131 SOCKET_POINT_ARRAY(verts, "Vertices", array<float3>());
132 SOCKET_INT_ARRAY(shader, "Shader", array<int>());
133 SOCKET_BOOLEAN_ARRAY(smooth, "Smooth", array<bool>());
134
135 return type;
136 }
137
Mesh(const NodeType * node_type_,Type geom_type_)138 Mesh::Mesh(const NodeType *node_type_, Type geom_type_)
139 : Geometry(node_type_, geom_type_), subd_attributes(this, ATTR_PRIM_SUBD)
140 {
141 vert_offset = 0;
142
143 patch_offset = 0;
144 face_offset = 0;
145 corner_offset = 0;
146
147 num_subd_verts = 0;
148
149 num_ngons = 0;
150
151 subdivision_type = SUBDIVISION_NONE;
152 subd_params = NULL;
153
154 patch_table = NULL;
155 }
156
Mesh()157 Mesh::Mesh() : Mesh(node_type, Geometry::MESH)
158 {
159 }
160
~Mesh()161 Mesh::~Mesh()
162 {
163 delete patch_table;
164 delete subd_params;
165 }
166
resize_mesh(int numverts,int numtris)167 void Mesh::resize_mesh(int numverts, int numtris)
168 {
169 verts.resize(numverts);
170 triangles.resize(numtris * 3);
171 shader.resize(numtris);
172 smooth.resize(numtris);
173
174 if (subd_faces.size()) {
175 triangle_patch.resize(numtris);
176 vert_patch_uv.resize(numverts);
177 }
178
179 attributes.resize();
180 }
181
reserve_mesh(int numverts,int numtris)182 void Mesh::reserve_mesh(int numverts, int numtris)
183 {
184 /* reserve space to add verts and triangles later */
185 verts.reserve(numverts);
186 triangles.reserve(numtris * 3);
187 shader.reserve(numtris);
188 smooth.reserve(numtris);
189
190 if (subd_faces.size()) {
191 triangle_patch.reserve(numtris);
192 vert_patch_uv.reserve(numverts);
193 }
194
195 attributes.resize(true);
196 }
197
resize_subd_faces(int numfaces,int num_ngons_,int numcorners)198 void Mesh::resize_subd_faces(int numfaces, int num_ngons_, int numcorners)
199 {
200 subd_faces.resize(numfaces);
201 subd_face_corners.resize(numcorners);
202 num_ngons = num_ngons_;
203
204 subd_attributes.resize();
205 }
206
reserve_subd_faces(int numfaces,int num_ngons_,int numcorners)207 void Mesh::reserve_subd_faces(int numfaces, int num_ngons_, int numcorners)
208 {
209 subd_faces.reserve(numfaces);
210 subd_face_corners.reserve(numcorners);
211 num_ngons = num_ngons_;
212
213 subd_attributes.resize(true);
214 }
215
clear(bool preserve_voxel_data)216 void Mesh::clear(bool preserve_voxel_data)
217 {
218 Geometry::clear();
219
220 /* clear all verts and triangles */
221 verts.clear();
222 triangles.clear();
223 shader.clear();
224 smooth.clear();
225
226 triangle_patch.clear();
227 vert_patch_uv.clear();
228
229 subd_faces.clear();
230 subd_face_corners.clear();
231
232 num_subd_verts = 0;
233
234 subd_creases.clear();
235
236 subd_attributes.clear();
237 attributes.clear(preserve_voxel_data);
238
239 vert_to_stitching_key_map.clear();
240 vert_stitching_map.clear();
241
242 delete patch_table;
243 patch_table = NULL;
244 }
245
clear()246 void Mesh::clear()
247 {
248 clear(false);
249 }
250
add_vertex(float3 P)251 void Mesh::add_vertex(float3 P)
252 {
253 verts.push_back_reserved(P);
254
255 if (subd_faces.size()) {
256 vert_patch_uv.push_back_reserved(make_float2(0.0f, 0.0f));
257 }
258 }
259
add_vertex_slow(float3 P)260 void Mesh::add_vertex_slow(float3 P)
261 {
262 verts.push_back_slow(P);
263
264 if (subd_faces.size()) {
265 vert_patch_uv.push_back_slow(make_float2(0.0f, 0.0f));
266 }
267 }
268
add_triangle(int v0,int v1,int v2,int shader_,bool smooth_)269 void Mesh::add_triangle(int v0, int v1, int v2, int shader_, bool smooth_)
270 {
271 triangles.push_back_reserved(v0);
272 triangles.push_back_reserved(v1);
273 triangles.push_back_reserved(v2);
274 shader.push_back_reserved(shader_);
275 smooth.push_back_reserved(smooth_);
276
277 if (subd_faces.size()) {
278 triangle_patch.push_back_reserved(-1);
279 }
280 }
281
add_subd_face(int * corners,int num_corners,int shader_,bool smooth_)282 void Mesh::add_subd_face(int *corners, int num_corners, int shader_, bool smooth_)
283 {
284 int start_corner = subd_face_corners.size();
285
286 for (int i = 0; i < num_corners; i++) {
287 subd_face_corners.push_back_reserved(corners[i]);
288 }
289
290 int ptex_offset = 0;
291
292 if (subd_faces.size()) {
293 SubdFace &s = subd_faces[subd_faces.size() - 1];
294 ptex_offset = s.ptex_offset + s.num_ptex_faces();
295 }
296
297 SubdFace face = {start_corner, num_corners, shader_, smooth_, ptex_offset};
298 subd_faces.push_back_reserved(face);
299 }
300
copy_center_to_motion_step(const int motion_step)301 void Mesh::copy_center_to_motion_step(const int motion_step)
302 {
303 Attribute *attr_mP = attributes.find(ATTR_STD_MOTION_VERTEX_POSITION);
304
305 if (attr_mP) {
306 Attribute *attr_mN = attributes.find(ATTR_STD_MOTION_VERTEX_NORMAL);
307 Attribute *attr_N = attributes.find(ATTR_STD_VERTEX_NORMAL);
308 float3 *P = &verts[0];
309 float3 *N = (attr_N) ? attr_N->data_float3() : NULL;
310 size_t numverts = verts.size();
311
312 memcpy(attr_mP->data_float3() + motion_step * numverts, P, sizeof(float3) * numverts);
313 if (attr_mN)
314 memcpy(attr_mN->data_float3() + motion_step * numverts, N, sizeof(float3) * numverts);
315 }
316 }
317
get_uv_tiles(ustring map,unordered_set<int> & tiles)318 void Mesh::get_uv_tiles(ustring map, unordered_set<int> &tiles)
319 {
320 Attribute *attr, *subd_attr;
321
322 if (map.empty()) {
323 attr = attributes.find(ATTR_STD_UV);
324 subd_attr = subd_attributes.find(ATTR_STD_UV);
325 }
326 else {
327 attr = attributes.find(map);
328 subd_attr = subd_attributes.find(map);
329 }
330
331 if (attr) {
332 attr->get_uv_tiles(this, ATTR_PRIM_GEOMETRY, tiles);
333 }
334 if (subd_attr) {
335 subd_attr->get_uv_tiles(this, ATTR_PRIM_SUBD, tiles);
336 }
337 }
338
compute_bounds()339 void Mesh::compute_bounds()
340 {
341 BoundBox bnds = BoundBox::empty;
342 size_t verts_size = verts.size();
343
344 if (verts_size > 0) {
345 for (size_t i = 0; i < verts_size; i++)
346 bnds.grow(verts[i]);
347
348 Attribute *attr = attributes.find(ATTR_STD_MOTION_VERTEX_POSITION);
349 if (use_motion_blur && attr) {
350 size_t steps_size = verts.size() * (motion_steps - 1);
351 float3 *vert_steps = attr->data_float3();
352
353 for (size_t i = 0; i < steps_size; i++)
354 bnds.grow(vert_steps[i]);
355 }
356
357 if (!bnds.valid()) {
358 bnds = BoundBox::empty;
359
360 /* skip nan or inf coordinates */
361 for (size_t i = 0; i < verts_size; i++)
362 bnds.grow_safe(verts[i]);
363
364 if (use_motion_blur && attr) {
365 size_t steps_size = verts.size() * (motion_steps - 1);
366 float3 *vert_steps = attr->data_float3();
367
368 for (size_t i = 0; i < steps_size; i++)
369 bnds.grow_safe(vert_steps[i]);
370 }
371 }
372 }
373
374 if (!bnds.valid()) {
375 /* empty mesh */
376 bnds.grow(make_float3(0.0f, 0.0f, 0.0f));
377 }
378
379 bounds = bnds;
380 }
381
apply_transform(const Transform & tfm,const bool apply_to_motion)382 void Mesh::apply_transform(const Transform &tfm, const bool apply_to_motion)
383 {
384 transform_normal = transform_transposed_inverse(tfm);
385
386 /* apply to mesh vertices */
387 for (size_t i = 0; i < verts.size(); i++)
388 verts[i] = transform_point(&tfm, verts[i]);
389
390 if (apply_to_motion) {
391 Attribute *attr = attributes.find(ATTR_STD_MOTION_VERTEX_POSITION);
392
393 if (attr) {
394 size_t steps_size = verts.size() * (motion_steps - 1);
395 float3 *vert_steps = attr->data_float3();
396
397 for (size_t i = 0; i < steps_size; i++)
398 vert_steps[i] = transform_point(&tfm, vert_steps[i]);
399 }
400
401 Attribute *attr_N = attributes.find(ATTR_STD_MOTION_VERTEX_NORMAL);
402
403 if (attr_N) {
404 Transform ntfm = transform_normal;
405 size_t steps_size = verts.size() * (motion_steps - 1);
406 float3 *normal_steps = attr_N->data_float3();
407
408 for (size_t i = 0; i < steps_size; i++)
409 normal_steps[i] = normalize(transform_direction(&ntfm, normal_steps[i]));
410 }
411 }
412 }
413
add_face_normals()414 void Mesh::add_face_normals()
415 {
416 /* don't compute if already there */
417 if (attributes.find(ATTR_STD_FACE_NORMAL))
418 return;
419
420 /* get attributes */
421 Attribute *attr_fN = attributes.add(ATTR_STD_FACE_NORMAL);
422 float3 *fN = attr_fN->data_float3();
423
424 /* compute face normals */
425 size_t triangles_size = num_triangles();
426
427 if (triangles_size) {
428 float3 *verts_ptr = verts.data();
429
430 for (size_t i = 0; i < triangles_size; i++) {
431 fN[i] = get_triangle(i).compute_normal(verts_ptr);
432 }
433 }
434
435 /* expected to be in local space */
436 if (transform_applied) {
437 Transform ntfm = transform_inverse(transform_normal);
438
439 for (size_t i = 0; i < triangles_size; i++)
440 fN[i] = normalize(transform_direction(&ntfm, fN[i]));
441 }
442 }
443
add_vertex_normals()444 void Mesh::add_vertex_normals()
445 {
446 bool flip = transform_negative_scaled;
447 size_t verts_size = verts.size();
448 size_t triangles_size = num_triangles();
449
450 /* static vertex normals */
451 if (!attributes.find(ATTR_STD_VERTEX_NORMAL) && triangles_size) {
452 /* get attributes */
453 Attribute *attr_fN = attributes.find(ATTR_STD_FACE_NORMAL);
454 Attribute *attr_vN = attributes.add(ATTR_STD_VERTEX_NORMAL);
455
456 float3 *fN = attr_fN->data_float3();
457 float3 *vN = attr_vN->data_float3();
458
459 /* compute vertex normals */
460 memset(vN, 0, verts.size() * sizeof(float3));
461
462 for (size_t i = 0; i < triangles_size; i++) {
463 for (size_t j = 0; j < 3; j++) {
464 vN[get_triangle(i).v[j]] += fN[i];
465 }
466 }
467
468 for (size_t i = 0; i < verts_size; i++) {
469 vN[i] = normalize(vN[i]);
470 if (flip) {
471 vN[i] = -vN[i];
472 }
473 }
474 }
475
476 /* motion vertex normals */
477 Attribute *attr_mP = attributes.find(ATTR_STD_MOTION_VERTEX_POSITION);
478 Attribute *attr_mN = attributes.find(ATTR_STD_MOTION_VERTEX_NORMAL);
479
480 if (has_motion_blur() && attr_mP && !attr_mN && triangles_size) {
481 /* create attribute */
482 attr_mN = attributes.add(ATTR_STD_MOTION_VERTEX_NORMAL);
483
484 for (int step = 0; step < motion_steps - 1; step++) {
485 float3 *mP = attr_mP->data_float3() + step * verts.size();
486 float3 *mN = attr_mN->data_float3() + step * verts.size();
487
488 /* compute */
489 memset(mN, 0, verts.size() * sizeof(float3));
490
491 for (size_t i = 0; i < triangles_size; i++) {
492 for (size_t j = 0; j < 3; j++) {
493 float3 fN = get_triangle(i).compute_normal(mP);
494 mN[get_triangle(i).v[j]] += fN;
495 }
496 }
497
498 for (size_t i = 0; i < verts_size; i++) {
499 mN[i] = normalize(mN[i]);
500 if (flip) {
501 mN[i] = -mN[i];
502 }
503 }
504 }
505 }
506
507 /* subd vertex normals */
508 if (!subd_attributes.find(ATTR_STD_VERTEX_NORMAL) && subd_faces.size()) {
509 /* get attributes */
510 Attribute *attr_vN = subd_attributes.add(ATTR_STD_VERTEX_NORMAL);
511 float3 *vN = attr_vN->data_float3();
512
513 /* compute vertex normals */
514 memset(vN, 0, verts.size() * sizeof(float3));
515
516 for (size_t i = 0; i < subd_faces.size(); i++) {
517 SubdFace &face = subd_faces[i];
518 float3 fN = face.normal(this);
519
520 for (size_t j = 0; j < face.num_corners; j++) {
521 size_t corner = subd_face_corners[face.start_corner + j];
522 vN[corner] += fN;
523 }
524 }
525
526 for (size_t i = 0; i < verts_size; i++) {
527 vN[i] = normalize(vN[i]);
528 if (flip) {
529 vN[i] = -vN[i];
530 }
531 }
532 }
533 }
534
add_undisplaced()535 void Mesh::add_undisplaced()
536 {
537 AttributeSet &attrs = (subdivision_type == SUBDIVISION_NONE) ? attributes : subd_attributes;
538
539 /* don't compute if already there */
540 if (attrs.find(ATTR_STD_POSITION_UNDISPLACED)) {
541 return;
542 }
543
544 /* get attribute */
545 Attribute *attr = attrs.add(ATTR_STD_POSITION_UNDISPLACED);
546 attr->flags |= ATTR_SUBDIVIDED;
547
548 float3 *data = attr->data_float3();
549
550 /* copy verts */
551 size_t size = attr->buffer_size(this, attrs.prim);
552
553 /* Center points for ngons aren't stored in Mesh::verts but are included in size since they will
554 * be calculated later, we subtract them from size here so we don't have an overflow while
555 * copying.
556 */
557 size -= num_ngons * attr->data_sizeof();
558
559 if (size) {
560 memcpy(data, verts.data(), size);
561 }
562 }
563
pack_shaders(Scene * scene,uint * tri_shader)564 void Mesh::pack_shaders(Scene *scene, uint *tri_shader)
565 {
566 uint shader_id = 0;
567 uint last_shader = -1;
568 bool last_smooth = false;
569
570 size_t triangles_size = num_triangles();
571 int *shader_ptr = shader.data();
572
573 for (size_t i = 0; i < triangles_size; i++) {
574 if (shader_ptr[i] != last_shader || last_smooth != smooth[i]) {
575 last_shader = shader_ptr[i];
576 last_smooth = smooth[i];
577 Shader *shader = (last_shader < used_shaders.size()) ? used_shaders[last_shader] :
578 scene->default_surface;
579 shader_id = scene->shader_manager->get_shader_id(shader, last_smooth);
580 }
581
582 tri_shader[i] = shader_id;
583 }
584 }
585
pack_normals(float4 * vnormal)586 void Mesh::pack_normals(float4 *vnormal)
587 {
588 Attribute *attr_vN = attributes.find(ATTR_STD_VERTEX_NORMAL);
589 if (attr_vN == NULL) {
590 /* Happens on objects with just hair. */
591 return;
592 }
593
594 bool do_transform = transform_applied;
595 Transform ntfm = transform_normal;
596
597 float3 *vN = attr_vN->data_float3();
598 size_t verts_size = verts.size();
599
600 for (size_t i = 0; i < verts_size; i++) {
601 float3 vNi = vN[i];
602
603 if (do_transform)
604 vNi = safe_normalize(transform_direction(&ntfm, vNi));
605
606 vnormal[i] = make_float4(vNi.x, vNi.y, vNi.z, 0.0f);
607 }
608 }
609
pack_verts(const vector<uint> & tri_prim_index,uint4 * tri_vindex,uint * tri_patch,float2 * tri_patch_uv,size_t vert_offset,size_t tri_offset)610 void Mesh::pack_verts(const vector<uint> &tri_prim_index,
611 uint4 *tri_vindex,
612 uint *tri_patch,
613 float2 *tri_patch_uv,
614 size_t vert_offset,
615 size_t tri_offset)
616 {
617 size_t verts_size = verts.size();
618
619 if (verts_size && subd_faces.size()) {
620 float2 *vert_patch_uv_ptr = vert_patch_uv.data();
621
622 for (size_t i = 0; i < verts_size; i++) {
623 tri_patch_uv[i] = vert_patch_uv_ptr[i];
624 }
625 }
626
627 size_t triangles_size = num_triangles();
628
629 for (size_t i = 0; i < triangles_size; i++) {
630 Triangle t = get_triangle(i);
631 tri_vindex[i] = make_uint4(t.v[0] + vert_offset,
632 t.v[1] + vert_offset,
633 t.v[2] + vert_offset,
634 tri_prim_index[i + tri_offset]);
635
636 tri_patch[i] = (!subd_faces.size()) ? -1 : (triangle_patch[i] * 8 + patch_offset);
637 }
638 }
639
pack_patches(uint * patch_data,uint vert_offset,uint face_offset,uint corner_offset)640 void Mesh::pack_patches(uint *patch_data, uint vert_offset, uint face_offset, uint corner_offset)
641 {
642 size_t num_faces = subd_faces.size();
643 int ngons = 0;
644
645 for (size_t f = 0; f < num_faces; f++) {
646 SubdFace face = subd_faces[f];
647
648 if (face.is_quad()) {
649 int c[4];
650 memcpy(c, &subd_face_corners[face.start_corner], sizeof(int) * 4);
651
652 *(patch_data++) = c[0] + vert_offset;
653 *(patch_data++) = c[1] + vert_offset;
654 *(patch_data++) = c[2] + vert_offset;
655 *(patch_data++) = c[3] + vert_offset;
656
657 *(patch_data++) = f + face_offset;
658 *(patch_data++) = face.num_corners;
659 *(patch_data++) = face.start_corner + corner_offset;
660 *(patch_data++) = 0;
661 }
662 else {
663 for (int i = 0; i < face.num_corners; i++) {
664 int c[4];
665 c[0] = subd_face_corners[face.start_corner + mod(i + 0, face.num_corners)];
666 c[1] = subd_face_corners[face.start_corner + mod(i + 1, face.num_corners)];
667 c[2] = verts.size() - num_subd_verts + ngons;
668 c[3] = subd_face_corners[face.start_corner + mod(i - 1, face.num_corners)];
669
670 *(patch_data++) = c[0] + vert_offset;
671 *(patch_data++) = c[1] + vert_offset;
672 *(patch_data++) = c[2] + vert_offset;
673 *(patch_data++) = c[3] + vert_offset;
674
675 *(patch_data++) = f + face_offset;
676 *(patch_data++) = face.num_corners | (i << 16);
677 *(patch_data++) = face.start_corner + corner_offset;
678 *(patch_data++) = subd_face_corners.size() + ngons + corner_offset;
679 }
680
681 ngons++;
682 }
683 }
684 }
685
686 CCL_NAMESPACE_END
687