// Filename: eggPolygon.cxx // Created by: drose (16Jan99) // //////////////////////////////////////////////////////////////////// // // PANDA 3D SOFTWARE // Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved // // All use of this software is subject to the terms of the Panda 3d // Software license. You should have received a copy of this license // along with this source code; you will also find a current copy of // the license at http://etc.cmu.edu/panda3d/docs/license/ . // // To contact the maintainers of this program write to // panda3d-general@lists.sourceforge.net . // //////////////////////////////////////////////////////////////////// #include "eggPolygon.h" #include "eggGroupNode.h" #include "plane.h" #include "indent.h" #include TypeHandle EggPolygon::_type_handle; //////////////////////////////////////////////////////////////////// // Function: EggPolygon::cleanup // Access: Published, Virtual // Description: Cleans up modeling errors in whatever context this // makes sense. For instance, for a polygon, this calls // remove_doubled_verts(true). For a point, it calls // remove_nonunique_verts(). Returns true if the // primitive is valid, or false if it is degenerate. //////////////////////////////////////////////////////////////////// bool EggPolygon:: cleanup() { remove_doubled_verts(true); // Use calculate_normal() to determine if the polygon is degenerate. Normald normal; return calculate_normal(normal); } //////////////////////////////////////////////////////////////////// // Function: EggPolygon::calculate_normal // Access: Published // Description: Calculates the true polygon normal--the vector // pointing out of the front of the polygon--based on // the vertices. This does not return or change the // polygon's normal as set via set_normal(). // // The return value is true if the normal is computed // correctly, or false if the polygon is degenerate and // does not have at least three noncollinear vertices. //////////////////////////////////////////////////////////////////// bool EggPolygon:: calculate_normal(Normald &result, CoordinateSystem cs) const { result = Normald::zero(); // Project the polygon into each of the three major planes and // calculate the area of each 2-d projection. This becomes the // polygon normal. This works because the ratio between these // different areas corresponds to the angle at which the polygon is // tilted toward each plane. size_t num_verts = size(); for (size_t i = 0; i < num_verts; i++) { Vertexd p0 = get_vertex(i)->get_pos3(); Vertexd p1 = get_vertex((i + 1) % num_verts)->get_pos3(); result[0] += p0[1] * p1[2] - p0[2] * p1[1]; result[1] += p0[2] * p1[0] - p0[0] * p1[2]; result[2] += p0[0] * p1[1] - p0[1] * p1[0]; } if (!result.normalize()) { // The polygon is degenerate: it has zero area in each plane. return false; } if (cs == CS_default) { cs = get_default_coordinate_system(); } if (cs == CS_zup_left || cs == CS_yup_left) { // In a left-handed coordinate system, we must flip the result. result = -result; } return true; } //////////////////////////////////////////////////////////////////// // Function: EggPolygon::is_planar // Access: Published // Description: Returns true if all of the polygon's vertices lie // within the same plane, false otherwise. //////////////////////////////////////////////////////////////////// bool EggPolygon:: is_planar() const { if (size() <= 3) { // If we don't have more than three vertices, we can't be // non-planar. return true; } Normald normal; if (!calculate_normal(normal)) { // A degenerate polygon--all of the vertices are within one line, // or all in the same point--is technically planar. Not sure if // this is a useful return value or not. return true; } // There should be at least one vertex (actually, at least three) // since we have already shown that the polygon is nondegenerate. nassertr(!empty(), false); // Create a plane perpendicular to the polygon's normal, containing // the first vertex. const_iterator vi = begin(); LVecBase3d first_point = (*vi)->get_pos3(); Planed plane(normal, first_point); // And check that all of the remaining vertices are sufficiently // close to the plane. ++vi; while (vi != end()) { LVecBase3d this_point = (*vi)->get_pos3(); if (!this_point.almost_equal(first_point)) { double dist = plane.dist_to_plane(this_point); double tol = dist / length(this_point - first_point); if (!IS_THRESHOLD_ZERO(tol, 0.0001)) { // Nope, too far away--the polygon is nonplanar. return false; } } ++vi; } // All vertices are close enough to pass. return true; } //////////////////////////////////////////////////////////////////// // Function: EggPolygon::triangulate_in_place // Access: Published // Description: Subdivides the polygon into triangles and adds those // triangles to the parent group node in place of the // original polygon. Returns a pointer to the original // polygon, which is likely about to be destructed. // // If convex_also is true, both concave and convex // polygons will be subdivided into triangles; // otherwise, only concave polygons will be subdivided, // and convex polygons will be copied unchanged into the // container. //////////////////////////////////////////////////////////////////// PT(EggPolygon) EggPolygon:: triangulate_in_place(bool convex_also) { EggGroupNode *parent = get_parent(); nassertr(parent != (EggGroupNode *)NULL, this); PT(EggPolygon) save_me = this; parent->remove_child(this); triangulate_poly(parent, convex_also); return save_me; } //////////////////////////////////////////////////////////////////// // Function: EggPolygon::write // Access: Published, Virtual // Description: Writes the polygon to the indicated output stream in // Egg format. //////////////////////////////////////////////////////////////////// void EggPolygon:: write(ostream &out, int indent_level) const { write_header(out, indent_level, ""); write_body(out, indent_level+2); indent(out, indent_level) << "}\n"; } //////////////////////////////////////////////////////////////////// // Function: EggPolygon::decomp_concave // Access: Private // Description: Decomposes a concave polygon into triangles. Returns // true if successful, false if the polygon is // self-intersecting. //////////////////////////////////////////////////////////////////// bool EggPolygon:: decomp_concave(EggGroupNode *container, int asum, int x, int y) const { #define VX(p, c) p->coord[c] struct DecompVtx { int index; LPoint3d coord; struct DecompVtx *next; }; DecompVtx *p0, *p1, *p2, *t0, *vert; DecompVtx *m[3]; double xmin, xmax, ymin, ymax; int i, init, csum, chek; double a[3], b[3], c[3], s[3]; int num_verts = size(); nassertr(num_verts >= 3, false); /* Make linked list of verts */ vert = (DecompVtx *) alloca(sizeof(DecompVtx)); vert->index = 0; vert->coord = get_vertex(0)->get_pos3(); p1 = vert; for (i = 1; i < num_verts; i++) { p0 = (DecompVtx *) alloca(sizeof(DecompVtx)); p0->index = i; p0->coord = get_vertex(i)->get_pos3(); // There shouldn't be two consecutive identical vertices. If // there are, skip one. if (!(p0->coord == p1->coord)) { p1->next = p0; p1 = p0; } } p1->next = vert; p0 = vert; p1 = p0->next; p2 = p1->next; m[0] = p0; m[1] = p1; m[2] = p2; chek = 0; while (p0 != p2->next) { /* Polygon is self-intersecting so punt */ if (chek && m[0] == p0 && m[1] == p1 && m[2] == p2) { return false; } chek = 1; a[0] = VX(p1, y) - VX(p2, y); b[0] = VX(p2, x) - VX(p1, x); a[2] = VX(p0, y) - VX(p1, y); b[2] = VX(p1, x) - VX(p0, x); csum = ((b[0] * a[2] - b[2] * a[0] >= 0.0) ? 1 : 0); if (csum ^ asum) { /* current angle is concave */ p0 = p1; p1 = p2; p2 = p2->next; } else { /* current angle is convex */ xmin = (VX(p0, x) < VX(p1, x)) ? VX(p0, x) : VX(p1, x); if (xmin > VX(p2, x)) xmin = VX(p2, x); xmax = (VX(p0, x) > VX(p1, x)) ? VX(p0, x) : VX(p1, x); if (xmax < VX(p2, x)) xmax = VX(p2, x); ymin = (VX(p0, y) < VX(p1, y)) ? VX(p0, y) : VX(p1, y); if (ymin > VX(p2, y)) ymin = VX(p2, y); ymax = (VX(p0, y) > VX(p1, y)) ? VX(p0, y) : VX(p1, y); if (ymax < VX(p2, y)) ymax = VX(p2, y); for (init = 1, t0 = p2->next; t0 != p0; t0 = t0->next) { if (VX(t0, x) >= xmin && VX(t0, x) <= xmax && VX(t0, y) >= ymin && VX(t0, y) <= ymax) { if (init) { a[1] = VX(p2, y) - VX(p0, y); b[1] = VX(p0, x) - VX(p2, x); init = 0; c[0] = VX(p1, x) * VX(p2, y) - VX(p2, x) * VX(p1, y); c[1] = VX(p2, x) * VX(p0, y) - VX(p0, x) * VX(p2, y); c[2] = VX(p0, x) * VX(p1, y) - VX(p1, x) * VX(p0, y); } s[0] = a[0] * VX(t0, x) + b[0] * VX(t0, y) + c[0]; s[1] = a[1] * VX(t0, x) + b[1] * VX(t0, y) + c[1]; s[2] = a[2] * VX(t0, x) + b[2] * VX(t0, y) + c[2]; if (asum) { if (s[0] >= 0.0 && s[1] >= 0.0 && s[2] >= 0.0) break; } else { if (s[0] <= 0.0 && s[1] <= 0.0 && s[2] <= 0.0) break; } } } if (t0 != p0) { p0 = p1; p1 = p2; p2 = p2->next; } else { // Here's a triangle to output. PT(EggPolygon) triangle = new EggPolygon(*this); triangle->clear(); triangle->add_vertex(get_vertex(p0->index)); triangle->add_vertex(get_vertex(p1->index)); triangle->add_vertex(get_vertex(p2->index)); if (triangle->cleanup()) { container->add_child(triangle.p()); } p0->next = p1->next; p1 = p2; p2 = p2->next; m[0] = p0; m[1] = p1; m[2] = p2; chek = 0; } } } // One more triangle to output. PT(EggPolygon) triangle = new EggPolygon(*this); triangle->clear(); triangle->add_vertex(get_vertex(p0->index)); triangle->add_vertex(get_vertex(p1->index)); triangle->add_vertex(get_vertex(p2->index)); if (triangle->cleanup()) { container->add_child(triangle.p()); } return true; } //////////////////////////////////////////////////////////////////// // Function: EggPolygon::triangulate_poly // Access: Private // Description: Breaks a (possibly concave) higher-order polygon into // a series of constituent triangles. Fills the // container up with EggPolygons that represent the // triangles. Returns true if successful, false on // failure. // // If convex_also is true, both concave and convex // polygons will be subdivided into triangles; // otherwise, only concave polygons will be subdivided, // and convex polygons will be copied unchanged into the // container. // // It is assumed that the EggPolygon is not already a // child of any other group when this function is // called. //////////////////////////////////////////////////////////////////// bool EggPolygon:: triangulate_poly(EggGroupNode *container, bool convex_also) { LPoint3d p0, p1, as; double dx1, dy1, dx2, dy2, max; int i, flag, asum, csum, index, x, y, v0, v1, v, even; if (!cleanup()) { return false; } // First see if the polygon is already a triangle int num_verts = size(); if (num_verts == 3) { container->add_child(this); return true; } else if (num_verts < 3) { // Or if it's a degenerate polygon. return false; } // calculate signed areas as[0] = 0.0; as[1] = 0.0; as[2] = 0.0; for (i = 0; i < num_verts; i++) { p0 = get_vertex(i)->get_pos3(); p1 = get_vertex((i + 1) % num_verts)->get_pos3(); as[0] += p0[0] * p1[1] - p0[1] * p1[0]; as[1] += p0[0] * p1[2] - p0[2] * p1[0]; as[2] += p0[1] * p1[2] - p0[2] * p1[1]; } /* select largest signed area */ max = 0.0; index = 0; flag = 0; for (i = 0; i < 3; i++) { if (as[i] >= 0.0) { if (as[i] > max) { max = as[i]; index = i; flag = 1; } } else { as[i] = -as[i]; if (as[i] > max) { max = as[i]; index = i; flag = 0; } } } /* pointer offsets */ switch (index) { case 0: x = 0; y = 1; break; case 1: x = 0; y = 2; break; default: // case 2 x = 1; y = 2; break; } /* concave check */ p0 = get_vertex(0)->get_pos3(); p1 = get_vertex(1)->get_pos3(); dx1 = p1[x] - p0[x]; dy1 = p1[y] - p0[y]; p0 = p1; p1 = get_vertex(2)->get_pos3(); dx2 = p1[x] - p0[x]; dy2 = p1[y] - p0[y]; asum = ((dx1 * dy2 - dx2 * dy1 >= 0.0) ? 1 : 0); for (i = 0; i < num_verts - 1; i++) { p0 = p1; p1 = get_vertex((i+3) % num_verts)->get_pos3(); dx1 = dx2; dy1 = dy2; dx2 = p1[x] - p0[x]; dy2 = p1[y] - p0[y]; csum = ((dx1 * dy2 - dx2 * dy1 >= 0.0) ? 1 : 0); if (csum ^ asum) { // It's a concave polygon. This is a little harder. return decomp_concave(container, flag, x, y); } } // It's a convex polygon. if (!convex_also) { // Make sure that it's also coplanar. If it's not, we should // triangulate it anyway. if (is_planar()) { container->add_child(this); return true; } } v0 = 0; v1 = 1; v = num_verts - 1; even = 1; /* * Convert to triangles only. Do not fan out from a single vertex * but zigzag into triangle strip. */ for (i = 0; i < num_verts - 2; i++) { if (even) { PT(EggPolygon) triangle = new EggPolygon(*this); triangle->clear(); triangle->add_vertex(get_vertex(v0)); triangle->add_vertex(get_vertex(v1)); triangle->add_vertex(get_vertex(v)); if (triangle->cleanup()) { container->add_child(triangle.p()); } v0 = v1; v1 = v; v = v0 + 1; } else { PT(EggPolygon) triangle = new EggPolygon(*this); triangle->clear(); triangle->add_vertex(get_vertex(v1)); triangle->add_vertex(get_vertex(v0)); triangle->add_vertex(get_vertex(v)); if (triangle->cleanup()) { container->add_child(triangle.p()); } v0 = v1; v1 = v; v = v0 - 1; } even = !even; } return true; }