/* Compute the plane equation for this polygon; copy the normal for * originally flat polygons, really compute it for polygons created by * sub-division from non-flat polygons. */ static inline void PolyPlane(PolyListNode *plnode, HPoint3 *plane) { if (plnode->pn) { *(Point3 *)plane = *plnode->pn; } else if (plnode->poly->flags & POLY_NONFLAT) { /* The polygon actually _IS_ flat -- BSPTreeCreate() is * responsible to make that sure, but we have to compute the * normal ourselves. We do not need to remember the normal because * when this function is called, then plnode has found its "home" * tree-node. */ PolyNormal(plnode->poly, (Point3 *)(void *)plane, true /* fourd */, false /* evert */, NULL, NULL); } else { *(Point3 *)plane = plnode->poly->pn; } plane->w = HPt3DotPt3(&plnode->poly->v[0]->pt, (Point3 *)(void *)plane); }
void UKismetMathLibrary::MinimumAreaRectangle(class UObject* WorldContextObject, const TArray<FVector>& InVerts, const FVector& SampleSurfaceNormal, FVector& OutRectCenter, FRotator& OutRectRotation, float& OutSideLengthX, float& OutSideLengthY, bool bDebugDraw) { float MinArea = -1.f; float CurrentArea = -1.f; FVector SupportVectorA, SupportVectorB; FVector RectSideA, RectSideB; float MinDotResultA, MinDotResultB, MaxDotResultA, MaxDotResultB; FVector TestEdge; float TestEdgeDot = 0.f; FVector PolyNormal(0.f, 0.f, 1.f); TArray<int32> PolyVertIndices; // Bail if we receive an empty InVerts array if( InVerts.Num() == 0 ) { return; } // Compute the approximate normal of the poly, using the direction of SampleSurfaceNormal for guidance PolyNormal = (InVerts[InVerts.Num() / 3] - InVerts[0]) ^ (InVerts[InVerts.Num() * 2 / 3] - InVerts[InVerts.Num() / 3]); if( (PolyNormal | SampleSurfaceNormal) < 0.f ) { PolyNormal = -PolyNormal; } // Transform the sample points to 2D FMatrix SurfaceNormalMatrix = FRotationMatrix::MakeFromZX(PolyNormal, FVector(1.f, 0.f, 0.f)); TArray<FVector> TransformedVerts; OutRectCenter = FVector(0.f); for( int32 Idx = 0; Idx < InVerts.Num(); ++Idx ) { OutRectCenter += InVerts[Idx]; TransformedVerts.Add(SurfaceNormalMatrix.InverseTransformVector(InVerts[Idx])); } OutRectCenter /= InVerts.Num(); // Compute the convex hull of the sample points ConvexHull2D::ComputeConvexHull(TransformedVerts, PolyVertIndices); // Minimum area rectangle as computed by http://www.geometrictools.com/Documentation/MinimumAreaRectangle.pdf for( int32 Idx = 1; Idx < PolyVertIndices.Num() - 1; ++Idx ) { SupportVectorA = (TransformedVerts[PolyVertIndices[Idx]] - TransformedVerts[PolyVertIndices[Idx-1]]).SafeNormal(); SupportVectorA.Z = 0.f; SupportVectorB.X = -SupportVectorA.Y; SupportVectorB.Y = SupportVectorA.X; SupportVectorB.Z = 0.f; MinDotResultA = MinDotResultB = MaxDotResultA = MaxDotResultB = 0.f; for (int TestVertIdx = 1; TestVertIdx < PolyVertIndices.Num(); ++TestVertIdx ) { TestEdge = TransformedVerts[PolyVertIndices[TestVertIdx]] - TransformedVerts[PolyVertIndices[0]]; TestEdgeDot = SupportVectorA | TestEdge; if( TestEdgeDot < MinDotResultA ) { MinDotResultA = TestEdgeDot; } else if(TestEdgeDot > MaxDotResultA ) { MaxDotResultA = TestEdgeDot; } TestEdgeDot = SupportVectorB | TestEdge; if( TestEdgeDot < MinDotResultB ) { MinDotResultB = TestEdgeDot; } else if( TestEdgeDot > MaxDotResultB ) { MaxDotResultB = TestEdgeDot; } } CurrentArea = (MaxDotResultA - MinDotResultA) * (MaxDotResultB - MinDotResultB); if( MinArea < 0.f || CurrentArea < MinArea ) { MinArea = CurrentArea; RectSideA = SupportVectorA * (MaxDotResultA - MinDotResultA); RectSideB = SupportVectorB * (MaxDotResultB - MinDotResultB); } } RectSideA = SurfaceNormalMatrix.TransformVector(RectSideA); RectSideB = SurfaceNormalMatrix.TransformVector(RectSideB); OutRectRotation = FRotationMatrix::MakeFromZX(PolyNormal, RectSideA).Rotator(); OutSideLengthX = RectSideA.Size(); OutSideLengthY = RectSideB.Size(); if( bDebugDraw ) { DrawDebugSphere(GEngine->GetWorldFromContextObject(WorldContextObject), OutRectCenter, 10.f, 12, FColor::Yellow, true); DrawDebugCoordinateSystem(GEngine->GetWorldFromContextObject(WorldContextObject), OutRectCenter, SurfaceNormalMatrix.Rotator(), 100.f, true); DrawDebugLine(GEngine->GetWorldFromContextObject(WorldContextObject), OutRectCenter - RectSideA * 0.5f + FVector(0,0,10.f), OutRectCenter + RectSideA * 0.5f + FVector(0,0,10.f), FColor::Green, true,-1, 0, 5.f); DrawDebugLine(GEngine->GetWorldFromContextObject(WorldContextObject), OutRectCenter - RectSideB * 0.5f + FVector(0,0,10.f), OutRectCenter + RectSideB * 0.5f + FVector(0,0,10.f), FColor::Blue, true,-1, 0, 5.f); } }
/* convert a PolyList into a linked list of PolyListNodes, subdivide * non-flat or concave polgons */ static PolyListNode * PolyListToLinkedPoyList(Transform T, Transform Tdual, Transform TxT, const void **tagged_app, PolyListNode **plistp, PolyList *pl, struct obstack *scratch) { PolyListNode *plist = NULL; int pnr; if (!plistp) { plistp = &plist; } PolyListComputeNormals(pl, PL_HASVN|PL_HASPN|PL_HASPFL); for (pnr = 0; pnr < pl->n_polys; pnr++) { PolyListNode *new_pn; Poly *poly; if (pl->p[pnr].flags & POLY_NOPOLY) { /* degenerated, just skip it */ continue; } poly = &pl->p[pnr]; poly->flags |= pl->geomflags; if (T && T != TM_IDENTITY) { poly = transform_poly(T, Tdual, TxT, poly, scratch); } switch (pl->p[pnr].n_vertices) { case 3: /* ok */ new_pn = new_poly_list_node(tagged_app, scratch); new_pn->poly = poly; ListPush(*plistp, new_pn); break; #if !HAVE_LIBGLU case 4: /* supported */ if (pl->p[pnr].flags & (POLY_NONFLAT|POLY_CONCAVE)) { /* split this polygon along a diagonal, if the polygon is * concave: split across the unique concave vertex. */ int concave; if (pl->p[pnr].flags & POLY_CONCAVE) { Point3 nu; /* We need to determine the concave vertex */ PolyNormal(poly, &nu, pl->geomflags & VERT_4D, false, NULL, &concave); } else { concave= 0; } split_quad_poly(concave, poly, plistp, tagged_app, scratch); } else { new_pn = new_poly_list_node(tagged_app, scratch); new_pn->poly = poly; ListPush(*plistp, new_pn); } break; default: if (pl->p[pnr].flags & (POLY_NONFLAT|POLY_CONCAVE)) { static int was_here; if (!was_here ) { GeomError(1, "Non-flat or concave polygons not supported yet.\n"); was_here = 1; } } new_pn = new_poly_list_node(tagged_app, scratch); new_pn->poly = poly; ListPush(*plistp, new_pn); break; #else case 4: /* if we want to be able to render polygons with * self-intersections "correctly", then we always have to use * the GLU tesselater for polygons with more than 4 vertices and * for non-convex quadrilaterals. We can handle non-flat * quadrilaterals ourselves. */ if ((pl->p[pnr].flags & (POLY_NONFLAT|POLY_CONCAVE)) == POLY_NONFLAT) { /* Split this polygon along a diagonal. Leave concave * quadrilaterals to the GLU tesselator; they could have * self-intersections. */ split_quad_poly(0, poly, plistp, tagged_app, scratch); } else if ((pl->p[pnr].flags & POLY_CONCAVE) == 0) { new_pn = new_poly_list_node(tagged_app, scratch); new_pn->poly = poly; ListPush(*plistp, new_pn); } break; /* otherwise fall into the > 4 vertices case and leave * everything to the GLU tesselator. */ default: { /* We use the GLU tesselator here, if available. It is not * necessary to reinvent the wheel; also, the OpenGL MG backend * also uses the tesselator (so we will get comparable shapes * w/o translucency). */ static GLUtesselator *glutess; struct tess_data tessdata[1]; VARARRAY2(dv, GLdouble, poly->n_vertices, 3); Vertex **vp; int i; if (glutess == NULL) { glutess = gluNewTess(); gluTessProperty(glutess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO); gluTessCallback(glutess, GLU_TESS_BEGIN_DATA, (GLvoid (*)())tess_begin_data); gluTessCallback(glutess, GLU_TESS_VERTEX_DATA, (GLvoid (*)())tess_vertex_data); gluTessCallback(glutess, GLU_TESS_COMBINE_DATA, (GLvoid (*)())tess_combine_data); } tessdata->trickyp = poly; tessdata->polyflags = poly->flags; tessdata->pn = &poly->pn; tessdata->scratch = scratch; tessdata->plistp = plistp; tessdata->tagged_app = tagged_app; /* tell GLU what we think is a good approximation for the normal */ gluTessNormal(glutess, poly->pn.x, poly->pn.y, poly->pn.z); /* rest is done in the callback functions */ gluTessBeginPolygon(glutess, tessdata); gluTessBeginContour(glutess); for (i = 0, vp = poly->v; i < poly->n_vertices; i++, vp++) { HPt3Coord w = (*vp)->pt.w ? (*vp)->pt.w : 1e20; if (w == 1.0) { dv[i][0] = (*vp)->pt.x; dv[i][1] = (*vp)->pt.y; dv[i][2] = (*vp)->pt.z; } else { dv[i][0] = (*vp)->pt.x / w; dv[i][1] = (*vp)->pt.y / w; dv[i][2] = (*vp)->pt.z / w; } gluTessVertex(glutess, dv[i], *vp); } gluTessEndContour(glutess); gluTessEndPolygon(glutess); break; /* out of switch */ } /* default */ #endif } /* switch */ } /* for */ return *plistp; }
/* Convert a Mesh into linked list of PolyListNodes, subdivide * non-flat or concave quadrilaterals. */ static PolyListNode * MeshToLinkedPolyList(Transform T, Transform Tdual, Transform TxT, const void **tagged_app, PolyListNode **plistp, Mesh *mesh, struct obstack *scratch) { PolyListNode *plist = NULL; Poly *qpoly; int v0 = 1, prev0v = 0; int u0 = 1, prev0u = 0; int u, v, prevu, prevv; int concave; int i; if (!plistp) { plistp = &plist; } MeshComputeNormals(mesh, MESH_N); if(mesh->geomflags & MESH_UWRAP) { v0 = 0, prev0v = mesh->nv-1; } if(mesh->geomflags & MESH_VWRAP) { u0 = 0, prev0u = mesh->nu-1; } #define MESHIDX(u, v, mesh) ((v)*(mesh)->nu + (u)) qpoly = NULL; for(prevv = prev0v, v = v0; v < mesh->nv; prevv = v, v++) { for(prevu = prev0u, u = u0; u < mesh->nu; prevu = u, u++) { /* First try to create a single polygon, if that mesh-cell is * non-flat or concave, then sub-divide it. */ if (!qpoly) { qpoly = new_poly(4, NULL, scratch); for (i = 0; i < 4; i++) { qpoly->v[i] = obstack_alloc(scratch, sizeof(Vertex)); } } if (T && T != TM_IDENTITY) { meshv_to_polyv_trans(T, Tdual, TxT, qpoly->v[0], mesh, MESHIDX(prevu, prevv, mesh)); meshv_to_polyv_trans(T, Tdual, TxT, qpoly->v[1], mesh, MESHIDX(u, prevv, mesh)); meshv_to_polyv_trans(T, Tdual, TxT, qpoly->v[2], mesh, MESHIDX(u, v, mesh)); meshv_to_polyv_trans(T, Tdual, TxT, qpoly->v[3], mesh, MESHIDX(prevu, v, mesh)); } else { meshv_to_polyv(qpoly->v[0], mesh, MESHIDX(prevu, prevv, mesh)); meshv_to_polyv(qpoly->v[1], mesh, MESHIDX(u, prevv, mesh)); meshv_to_polyv(qpoly->v[2], mesh, MESHIDX(u, v, mesh)); meshv_to_polyv(qpoly->v[3], mesh, MESHIDX(prevu, v, mesh)); } if (mesh->geomflags & MESH_C) { qpoly->flags |= PL_HASVCOL; } if (mesh->geomflags & COLOR_ALPHA) { qpoly->flags |= COLOR_ALPHA; } if (mesh->geomflags & MESH_N) { qpoly->flags |= PL_HASVN; } if (mesh->geomflags & MESH_U) { qpoly->flags |= PL_HASST; } PolyNormal(qpoly, &qpoly->pn, mesh->geomflags & VERT_4D, false, &qpoly->flags, &concave); qpoly->flags |= PL_HASPN; if (qpoly->flags & POLY_NOPOLY) { /* polygon is degenerated, we just do NOT draw it, edges are * drawn by other methods. Just do nothing, qpoly will be * re-used for the next quad. */ qpoly->flags = 0; } else if (qpoly->flags & (POLY_CONCAVE|POLY_NONFLAT)) { /* we need to split it */ split_quad_poly(concave, qpoly, plistp, tagged_app, scratch); qpoly = NULL; } else { PolyListNode *new_pn; new_pn = new_poly_list_node(tagged_app, scratch); new_pn->poly = qpoly; ListPush(*plistp, new_pn); qpoly = NULL; } } } #undef MESHIDX return *plistp; }
static PolyListNode * QuadToLinkedPolyList(Transform T, Transform Tdual, Transform TxT, const void **tagged_app, PolyListNode **plistp, Quad *quad, struct obstack *scratch) { PolyListNode *plist = NULL; Poly *qpoly; int i, j, concave; (void)Tdual; (void)TxT; if (!plistp) { plistp = &plist; } if(!(quad->geomflags & QUAD_N)) { QuadComputeNormals(quad); } qpoly = NULL; for (i = 0; i < quad->maxquad; i++) { /* First try to create a single polygon, if that mesh-cell is * non-flat or concave, then sub-divide it. */ if (!qpoly) { qpoly = new_poly(4, NULL, scratch); for (j = 0; j < 4; j++) { qpoly->v[j] = obstack_alloc(scratch, sizeof(Vertex)); } } if (T && T != TM_IDENTITY) { for (j = 0; j < 4; j++) { memset(qpoly->v[j], 0, sizeof(Vertex)); HPt3Transform(T, &quad->p[i][j], &qpoly->v[j]->pt); NormalTransform(T, &quad->n[i][j], &qpoly->v[j]->vn); } } else { for (j = 0; j < 4; j++) { memset(qpoly->v[j], 0, sizeof(Vertex)); qpoly->v[j]->pt = quad->p[i][j]; qpoly->v[j]->vn = quad->n[i][j]; } } qpoly->flags |= PL_HASVN; if (quad->geomflags & QUAD_C) { qpoly->flags |= PL_HASVCOL; for (j = 0; j < 4; j++) { qpoly->v[j]->vcol = quad->c[i][j]; } } if (quad->geomflags & COLOR_ALPHA) { qpoly->flags |= COLOR_ALPHA; } PolyNormal(qpoly, &qpoly->pn, quad->geomflags & VERT_4D, false, &qpoly->flags, &concave); qpoly->flags |= PL_HASPN; if (qpoly->flags & POLY_NOPOLY) { /* degenerated, skip it, but reuse the memory region. */ qpoly->flags = 0; } else if (qpoly->flags & (POLY_CONCAVE|POLY_NONFLAT)) { /* we need to split it */ split_quad_poly(concave, qpoly, plistp, tagged_app, scratch); qpoly = NULL; } else { PolyListNode *new_pn; new_pn = new_poly_list_node(tagged_app, scratch); new_pn->poly = qpoly; ListPush(*plistp, new_pn); qpoly = NULL; } } return *plistp; }
/* Split plnode along plane. We know that plane really intersects * plnode->poly. We also know that plnode->poly is planar and convex. * * Given this assumptions we know that plnode->poly has to be split * into exactly two pieces. */ static inline void SplitPolyNode(PolyListNode *plnode, PolyListNode **front, PolyListNode **back, EdgeIntersection edges[2], struct obstack *scratch) { const void **tagged_app = plnode->tagged_app; Poly *poly = plnode->poly, savedp; VARARRAY(savedv, Vertex *, poly->n_vertices); Vertex *v0, *v1, **vpos; int istart[2], iend[2], i, nv[2]; Vertex *vstart[2], *vend[2]; #if BSPTREE_STATS ++n_tree_polys; #endif vstart[0] = vstart[1] = vend[0] = vend[1] = NULL; istart[0] = istart[1] = iend[0] = iend[1] = -1; /* first point of intersection */ if (fzero(edges[0].scp[0])) { v0 = poly->v[edges[0].v[0]]; if (fpos(edges[0].scp[1])) { istart[0] = edges[0].v[0]; iend[1] = edges[0].v[0]; } else { istart[1] = edges[0].v[0]; iend[0] = edges[0].v[0]; } } else if (fzero(edges[0].scp[1])) { v0 = poly->v[edges[0].v[1]]; if (fpos(edges[0].scp[0])) { istart[1] = edges[0].v[1]; iend[0] = edges[0].v[1]; } else { istart[0] = edges[0].v[1]; iend[1] = edges[0].v[1]; } } else { HPt3Coord mu0, mu1; Vertex *V0 = poly->v[edges[0].v[0]]; Vertex *V1 = poly->v[edges[0].v[1]]; v0 = obstack_alloc(scratch, sizeof(Vertex)); mu0 = edges[0].scp[1]/(edges[0].scp[1]-edges[0].scp[0]); #if 0 mu1 = edges[0].scp[0]/(edges[0].scp[0]-edges[0].scp[1]); #else mu1 = 1.0 - mu0; #endif /* Use denormalized variant; otherwise textures may come out wrong * because the homogeneous divisor is used for perspective * corrections. */ if (poly->flags & VERT_ST) { v0->st.s = mu0 * V0->st.s + mu1 * V1->st.s; v0->st.t = mu0 * V0->st.t + mu1 * V1->st.t; HPt3LinSumDenorm(mu0, &V0->pt, mu1, &V1->pt, &v0->pt); } else { HPt3LinSum(mu0, &V0->pt, mu1, &V1->pt, &v0->pt); } if (!finite(v0->pt.x + v0->pt.y + v0->pt.z)){ abort(); } if (poly->flags & VERT_C) { CoLinSum(mu0, &V0->vcol, mu1, &V1->vcol, &v0->vcol); } if (true || (poly->flags & VERT_N)) { /* The averaged vertex normals do not have an orientation, so * try to orient them w.r.t. the polygon normal before computing * the linear combination. */ if (Pt3Dot(&V0->vn, &poly->pn)*Pt3Dot(&V1->vn, &poly->pn) < 0) { Pt3Comb(-mu0, &V0->vn, mu1, &V1->vn, &v0->vn); } else { Pt3Comb(mu0, &V0->vn, mu1, &V1->vn, &v0->vn); } Pt3Unit(&v0->vn); } if (fpos(edges[0].scp[0])) { vstart[1] = vend[0] = v0; istart[1] = edges[0].v[1]; iend[0] = edges[0].v[0]; } else { vstart[0] = vend[1] = v0; istart[0] = edges[0].v[1]; iend[1] = edges[0].v[0]; } } /* second point of intersection */ if (fzero(edges[1].scp[0])) { v1 = poly->v[edges[1].v[0]]; if (fpos(edges[1].scp[1])) { istart[0] = edges[1].v[0]; iend[1] = edges[1].v[0]; } else { istart[1] = edges[1].v[0]; iend[0] = edges[1].v[0]; } } else if (fzero(edges[1].scp[1])) { v1 = poly->v[edges[1].v[1]]; if (fpos(edges[1].scp[0])) { istart[1] = edges[1].v[1]; iend[0] = edges[1].v[1]; } else { istart[0] = edges[1].v[1]; iend[1] = edges[1].v[1]; } } else { HPt3Coord mu0, mu1; Vertex *V0 = poly->v[edges[1].v[0]]; Vertex *V1 = poly->v[edges[1].v[1]]; v1 = obstack_alloc(scratch, sizeof(Vertex)); mu0 = edges[1].scp[1]/(edges[1].scp[1]-edges[1].scp[0]); #if 0 mu1 = edges[1].scp[0]/(edges[1].scp[0]-edges[1].scp[1]); #else mu1 = 1.0 - mu0; #endif if (poly->flags & VERT_ST) { v1->st.s = mu0 * V0->st.s + mu1 * V1->st.s; v1->st.t = mu0 * V0->st.t + mu1 * V1->st.t; HPt3LinSumDenorm(mu0, &V0->pt, mu1, &V1->pt, &v1->pt); } else { HPt3LinSum(mu0, &V0->pt, mu1, &V1->pt, &v1->pt); } if (!finite(v1->pt.x + v1->pt.y + v1->pt.z)) abort(); if (poly->flags & VERT_C) { CoLinSum(mu0, &V0->vcol, mu1, &V1->vcol, &v1->vcol); } if (true || (poly->flags & VERT_N)) { if (Pt3Dot(&V0->vn, &poly->pn)*Pt3Dot(&V1->vn, &poly->pn) < 0) { Pt3Comb(-mu0, &V0->vn, mu1, &V1->vn, &v1->vn); } else { Pt3Comb(mu0, &V0->vn, mu1, &V1->vn, &v1->vn); } Pt3Unit(&v1->vn); } if (fpos(edges[1].scp[0])) { vstart[1] = vend[0] = v1; istart[1] = edges[1].v[1]; iend[0] = edges[1].v[0]; } else { vstart[0] = vend[1] = v1; istart[0] = edges[1].v[1]; iend[1] = edges[1].v[0]; } } ListPush(*front, plnode); ListPush(*back, new_poly_list_node(tagged_app, scratch)); if ((poly->flags & POLY_NONFLAT)) { if (!(*front)->pn) { /* Compute the normal on the parent element to avoid numerical * instabilities on increasingly degenerated polygons. */ (*front)->pn = obstack_alloc(scratch, sizeof(Point3)); PolyNormal(poly, (*front)->pn, true /* 4d */, false /* evert */, NULL, NULL); } (*back)->pn = (*front)->pn; } for (i = 0; i < 2; i++) { nv[i] = iend[i] - istart[i] + 1; if (nv[i] < 0) { nv[i] += poly->n_vertices; } nv[i] += (vstart[i] != NULL) + (vend[i] != NULL); } if (poly->flags & POLY_SCRATCH) { savedp = *poly; memcpy(savedv, poly->v, poly->n_vertices*sizeof(Vertex *)); if (nv[0] <= poly->n_vertices) { poly->n_vertices = nv[0]; (*front)->poly = poly; (*back)->poly = new_poly(nv[1], NULL, scratch); } else { if (nv[1] > poly->n_vertices) { abort(); } poly->n_vertices = nv[1]; (*back)->poly = poly; (*front)->poly = new_poly(nv[0], NULL, scratch); } /* Attention: gcc had problems with this code snippet with * -fstrict-aliasing, the "#if 1" stuff seems to work. In the * "#else" version gcc somehow lost the "savedp.v = savedv" * assignment. I think this is a compiler bug. */ poly = &savedp; #if 1 poly->v = savedv; #else savedp.v = savedv; #endif } else { (*front)->poly = new_poly(nv[0], NULL, scratch); (*back)->poly = new_poly(nv[1], NULL, scratch); } for (i = 0; i < 2; i++) { PolyListNode *half = (i == 0) ? *front : *back; int j; vpos = half->poly->v; if (vstart[i] != NULL) { *vpos++ = vstart[i]; } if (istart[i] <= iend[i]) { for (j = istart[i]; j <= iend[i] && j < poly->n_vertices; j++) { *vpos++ = poly->v[j]; } } else { for (j = istart[i]; j < poly->n_vertices; j++) { *vpos++ = poly->v[j]; } for (j = 0; j <= iend[i]; j++) { *vpos++ = poly->v[j]; } } if (vend[i] != NULL) { *vpos++ = vend[i]; } half->poly->pcol = poly->pcol; half->poly->pn = poly->pn; half->poly->flags = poly->flags|POLY_SCRATCH; check_poly(half->poly); } }