/* bsp.c
  Copyright (C) 2005,2006,2007 Eugene K. Ressler, Jr.

This file is part of Sketch, a small, simple system for making
3d drawings with LaTeX and the PSTricks or TikZ package.

Sketch is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

Sketch is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Sketch; see the file COPYING.txt.  If not, see
http://www.gnu.org/copyleft */

#include <stdio.h>
#include "bsp.h"
#include "geomio.h"

DECLARE_DYNAMIC_ARRAY_FUNCS (BSP_POLYGON_ATTR, BSP_VERTEX_ATTR,
                            polygon_attr, elt, n_elts, NO_OTHER_INIT)
// ---- polylines --------------------------------------------------------------
    static BSP_POLYLINE_NODE *new_bsp_polyline_node (void *attr)
{
 BSP_POLYLINE_NODE *n = safe_malloc (sizeof *n);
 n->tag = BSP_POLYLINE;
 n->attr = attr;
 n->prev = n->next = n->mark = n->in = n->on = n->out = NULL;
 n->first_p = n->last_p = 0;
 init_box_3d (n->extent);
 init_polyline_3d (n->polyline);
 return n;
}

static void
delete_bsp_polyline_node (BSP_POLYLINE_NODE * node)
{
 clear_polyline_3d (node->polyline);
 safe_free (node);
}

static void
set_bsp_polyline_extent (BSP_POLYLINE_NODE * node)
{
 // set extent
 init_box_3d (node->extent);
 fold_min_max_polyline_3d (node->extent, node->polyline);
}

static void
init_bsp_polyline (BSP_POLYLINE_NODE * node, POLYLINE_3D * polyline)
{
 // initial polyline contains first and last points; split ones don't
 node->first_p = node->last_p = 1;
 copy_polyline_3d (node->polyline, polyline);
 set_bsp_polyline_extent (node);
}

static void
split_polyline_with_plane (BSP_POLYLINE_NODE * node,
                          PLANE * plane,
                          BSP_POLYLINE_NODE ** in_nodes,
                          BSP_POLYLINE_NODE ** on_nodes,
                          BSP_POLYLINE_NODE ** out_nodes)
{
 int i, j, i_side, current_side;
 BSP_POLYLINE_NODE *current, **list;
 VECTOR_3D v, dp;
 POINT_3D isect;
 FLOAT t;

 // initialize all the output lists to empty
 *in_nodes = *on_nodes = *out_nodes = NULL;

 // initialize the current polyline that's being "built", copying attributes
 // of the original
 current = new_bsp_polyline_node (node->attr);

 // copy source polygon's first_ attribute
 current->first_p = node->first_p;

 // j is the trail index as we step through vertices with head i
 j = 0;

 // copy first vertex of polyline onto current output polyline
 copy_pt_3d (pushed_polyline_3d_v (current->polyline), node->polyline->v[j]);

 // find side of cutting plane that first vertex is on.
 current_side =
   (S_IN | S_ON | S_OUT) & pt_side_of_plane (plane, node->polyline->v[j]);

 // loop through all directed segments j->i
 for (i = 1; i < node->polyline->n_vertices; j = i++)
   {
     i_side =
       (S_IN | S_ON | S_OUT) & pt_side_of_plane (plane,
                                                 node->polyline->v[i]);

#define HASH(Current, I) ((Current << 3) | I)

     switch (HASH (current_side, i_side))
       {

       case HASH (S_OUT, S_IN):
       case HASH (S_IN, S_OUT):

         // vertices straddle the plane; compute the intersection
         sub_pts_3d (v, node->polyline->v[i], node->polyline->v[j]);   // direction vector
         sub_pts_3d (dp, plane->p, node->polyline->v[j]);      // P - L
         t = dot_3d (dp, plane->n) / dot_3d (v, plane->n);     // parameter of intersect
         add_scaled_vec_to_pt_3d (isect, node->polyline->v[j], v, t);  // intersect

         // finish current polyline and add to current list
         copy_pt_3d (pushed_polyline_3d_v (current->polyline), isect);
         set_bsp_polyline_extent (current);
         list = current_side == S_IN ? in_nodes : out_nodes;
         current->in = (BSP_NODE *) * list;
         *list = current;

         // start a new one by adding the isect and new point
         current = new_bsp_polyline_node (node->attr);
         copy_pt_3d (pushed_polyline_3d_v (current->polyline), isect);
         copy_pt_3d (pushed_polyline_3d_v (current->polyline),
                     node->polyline->v[i]);
         current_side = i_side;
         break;

       case HASH (S_OUT, S_ON):
       case HASH (S_IN, S_ON):

         // line that was away from the plane joins it;
         // finish current polyline and add to current list
         copy_pt_3d (pushed_polyline_3d_v (current->polyline),
                     node->polyline->v[i]);
         set_bsp_polyline_extent (current);
         list = current_side == S_IN ? in_nodes : out_nodes;
         current->in = (BSP_NODE *) * list;
         *list = current;

         // start a new one if there are still vertices left to process
         if (i < node->polyline->n_vertices - 1)
           {
             current = new_bsp_polyline_node (node->attr);
             copy_pt_3d (pushed_polyline_3d_v (current->polyline),
                         node->polyline->v[i]);
             current_side = S_ON;
           }
         else
           {
             // copy last_p attribute from source
             current->last_p = node->last_p;
             current = NULL;
             current_side = 0;
           }
         break;

       case HASH (S_ON, S_OUT):
       case HASH (S_ON, S_IN):

         // line that was on the plane springs away from it;
         // if there is more than one point in the current polyline,
         // complete it and start a new one
         if (current->polyline->n_vertices > 1)
           {
             FLOAT *last_vertex = current->polyline->v[current->polyline->n_vertices - 1];     //remember last vertex
             set_bsp_polyline_extent (current);
             current->in = (BSP_NODE *) * on_nodes;
             *on_nodes = current;
             current = new_bsp_polyline_node (node->attr);
             copy_pt_3d (pushed_polyline_3d_v (current->polyline),
                         last_vertex);
           }
         // add the new vertex to the current polyline
         copy_pt_3d (pushed_polyline_3d_v (current->polyline),
                     node->polyline->v[i]);
         current_side = i_side;        // now either in or out
         break;

       default:

         // nothing has changed, so just add the new point
         // to the ccurrent polygon
         copy_pt_3d (pushed_polyline_3d_v (current->polyline),
                     node->polyline->v[i]);
         break;
       }
   }
 // finish the final polyline
 if (current)
   {
     // copy last_p attribute from source
     current->last_p = node->last_p;

     set_bsp_polyline_extent (current);
     list = (current_side & S_IN) ? in_nodes :
       (current_side & S_OUT) ? out_nodes : on_nodes;
     current->in = (BSP_NODE *) * list;
     *list = current;
   }
}

static void
insert_polyline (BSP_TREE * bsp, BSP_POLYLINE_NODE * node)
{
 if (*bsp == NULL)
   {
     *bsp = (BSP_NODE *) node;
   }
 else if ((*bsp)->tag == BSP_POLYLINE)
   {
     BSP_POLYLINE_NODE *bsp_node = (BSP_POLYLINE_NODE *) * bsp;
#ifdef EXPERIMENTAL_OPTIMIZATION
     node->in = bsp_node;
     *bsp = (BSP_NODE *) node;
#else
     insert_polyline ((BSP_TREE *) & bsp_node->in, node);
#endif
   }
 else
   {                           // BSP_POLYGON
     BSP_POLYGON_NODE *bsp_node = (BSP_POLYGON_NODE *) * bsp;
     int side = polyline_side_of_plane (node->polyline, bsp_node->plane);
     if (side == S_IN)
       insert_polyline (&bsp_node->in, node);
     else if (side == S_ON)
       insert_polyline (&bsp_node->on, node);
     else if (side == S_OUT)
       insert_polyline (&bsp_node->out, node);
     else
       {                       // S_SPLIT
         BSP_POLYLINE_NODE *in_polylines, *on_polylines, *out_polylines,
           *p, *p_next;
         split_polyline_with_plane (node, bsp_node->plane, &in_polylines,
                                    &on_polylines, &out_polylines);
         // remove polylines from lists and add to respective bsp subtrees recursively
         for (p = in_polylines; p; p = p_next)
           {
             p_next = (BSP_POLYLINE_NODE *) p->in;
             p->in = NULL;
             insert_polyline (&bsp_node->in, p);
           }
         for (p = on_polylines; p; p = p_next)
           {
             p_next = (BSP_POLYLINE_NODE *) p->in;
             p->in = NULL;
             insert_polyline (&bsp_node->on, p);
           }
         for (p = out_polylines; p; p = p_next)
           {
             p_next = (BSP_POLYLINE_NODE *) p->in;
             p->in = NULL;
             insert_polyline (&bsp_node->out, p);
           }
         delete_bsp_polyline_node (node);
       }
   }
}

void
add_polyline_to_bsp (BSP_TREE * bsp, POLYLINE_3D * polyline, void *attr)
{
 BSP_POLYLINE_NODE *node = new_bsp_polyline_node (attr);
 init_bsp_polyline (node, polyline);
 insert_polyline (bsp, node);
}

// ---- polygons ---------------------------------------------------------------

static BSP_POLYGON_NODE *
new_bsp_polygon_node (void *attr)
{
 BSP_POLYGON_NODE *n = safe_malloc (sizeof *n);
 n->tag = BSP_POLYGON;
 n->attr = attr;
 n->prev = n->next = n->mark = n->in = n->on = n->out = NULL;
 init_box_3d (n->extent);
 init_polygon_3d (n->polygon);
 init_polygon_attr (n->polygon_attr);
 return n;
}

static void
set_bsp_polygon_extent (BSP_POLYGON_NODE * node)
{
 // set extent
 init_box_3d (node->extent);
 fold_min_max_polygon_3d (node->extent, node->polygon);
}

static void
init_bsp_polygon_node (BSP_POLYGON_NODE * node, POLYGON_3D * polygon)
{
 int i;

 // fill in the polygon verticies
 copy_polygon_3d (node->polygon, polygon);

 // fill in the plane
 find_polygon_plane (node->plane, polygon);

 // mark all edges as border edges
 setup_polygon_attr (node->polygon_attr, polygon->n_sides);
 for (i = 0; i < polygon->n_sides; i++)
   node->polygon_attr->elt[i].border_p = 1;
 node->polygon_attr->n_elts = polygon->n_sides;

 // fill in the extent
 set_bsp_polygon_extent (node);
}

static void
delete_bsp_polygon_node (BSP_POLYGON_NODE * node)
{
 clear_polygon_3d (node->polygon);
 clear_polygon_attr (node->polygon_attr);
 safe_free (node);
}

// decide whether a j->i edge of the the new polygon that has
// been split from parent is part of the border.
static int
is_new_border_p (BSP_VERTEX_ATTR * i_attr,
                BSP_VERTEX_ATTR * j_attr,
                BSP_POLYGON_ATTR * parent_attr, int parent_n_sides)
{
 int parent_is_border_p;

 if (parent_attr->n_elts != parent_n_sides)
   die (no_line, "failed assumption on attribute size");
 parent_is_border_p = parent_attr->elt[j_attr->parent_vtx].border_p;
 if (!parent_is_border_p)
   return 0;

 if (i_attr->cut_p)
   {
     if (j_attr->cut_p)
       {
         return 0;
       }
     else
       {
         return i_attr->parent_vtx == j_attr->parent_vtx;
       }
   }
 return (i_attr->parent_vtx - j_attr->parent_vtx +
         parent_n_sides) % parent_n_sides == 1;
}

static void
decide_boundaries (BSP_POLYGON_NODE * new_node, BSP_POLYGON_NODE * node)
{
 int i, j, last_border_p;

 i = 0;
 j = new_node->polygon->n_sides - 1;
 last_border_p =
   is_new_border_p (&new_node->polygon_attr->elt[i],
                    &new_node->polygon_attr->elt[j],
                    node->polygon_attr, node->polygon->n_sides);
 for (j = i++; i < new_node->polygon->n_sides; j = i++)
   {
     new_node->polygon_attr->elt[j].border_p =
       is_new_border_p (&new_node->polygon_attr->elt[i],
                        &new_node->polygon_attr->elt[j],
                        node->polygon_attr, node->polygon->n_sides);
   }
 new_node->polygon_attr->elt[j].border_p = last_border_p;
}

static void
push_polygon_attr (BSP_POLYGON_NODE * node, int parent_vtx, int cut_p)
{
 BSP_VERTEX_ATTR *vertex_attr = pushed_polygon_attr_elt (node->polygon_attr);
 vertex_attr->border_p = 0;
 vertex_attr->parent_vtx = parent_vtx;
 vertex_attr->cut_p = cut_p;
}

static void
split_polygon_with_plane (BSP_POLYGON_NODE * node,
                         PLANE * plane,
                         BSP_POLYGON_NODE * in_node,
                         BSP_POLYGON_NODE * out_node)
{
 int i, j, i_side, j_side;
 VECTOR_3D v, dp;
 POINT_3D isect;
 FLOAT t;

 // reset fill pointers
 in_node->polygon->n_sides = out_node->polygon->n_sides = 0;
 in_node->polygon_attr->n_elts = out_node->polygon_attr->n_elts = 0;

 // process all pairs of vertices
 j = node->polygon->n_sides - 1;
 i_side = pt_side_of_plane (plane, node->polygon->v[j]);
 for (i = 0; i < node->polygon->n_sides; j = i++)
   {
     j_side = i_side;
     i_side = pt_side_of_plane (plane, node->polygon->v[i]);

     if ((i_side | j_side) == (S_IN | S_OUT))
       {

         // the two most recent points straddle the the plane
         // compute the intersection
         sub_pts_3d (v, node->polygon->v[i], node->polygon->v[j]);     // direction vector
         sub_pts_3d (dp, plane->p, node->polygon->v[j]);       // P - L
         t = dot_3d (dp, plane->n) / dot_3d (v, plane->n);     // parameter of intersect
         add_scaled_vec_to_pt_3d (isect, node->polygon->v[j], v, t);   // intersect

         // put intersect in both polygons
         copy_pt_3d (pushed_polygon_3d_v (in_node->polygon), isect);
         copy_pt_3d (pushed_polygon_3d_v (out_node->polygon), isect);

         if (i_side == S_IN)
           {
             // edge traversed from outside to in
             push_polygon_attr (out_node, j, 1);
             push_polygon_attr (in_node, j, 1);
             copy_pt_3d (pushed_polygon_3d_v (in_node->polygon),
                         node->polygon->v[i]);
             push_polygon_attr (in_node, i, 0);
           }
         else
           {
             // edge traversed from inside to out
             push_polygon_attr (out_node, j, 1);
             push_polygon_attr (in_node, j, 1);
             copy_pt_3d (pushed_polygon_3d_v (out_node->polygon),
                         node->polygon->v[i]);
             push_polygon_attr (out_node, i, 0);;
           }
       }
     else if (i_side & S_ON)
       {

         // the current vertex is on the plane
         // put it in both polygons
         copy_pt_3d (pushed_polygon_3d_v (in_node->polygon),
                     node->polygon->v[i]);
         copy_pt_3d (pushed_polygon_3d_v (out_node->polygon),
                     node->polygon->v[i]);
         push_polygon_attr (in_node, i, 0);
         push_polygon_attr (out_node, i, 0);
       }
     else
       {

         // the new vertex is not straddling nor on the plane
         // so we output it to the correct polygon
         if (i_side == S_IN)
           {
             copy_pt_3d (pushed_polygon_3d_v (in_node->polygon),
                         node->polygon->v[i]);
             push_polygon_attr (in_node, i, 0);
           }
         else
           {
             copy_pt_3d (pushed_polygon_3d_v (out_node->polygon),
                         node->polygon->v[i]);
             push_polygon_attr (out_node, i, 0);
           }
       }
   }
 // fill in the planes
 find_polygon_plane (in_node->plane, in_node->polygon);
 find_polygon_plane (out_node->plane, out_node->polygon);

 // set up extents
 set_bsp_polygon_extent (in_node);
 set_bsp_polygon_extent (out_node);

 // make a pass around the in and out polygons to fill in boundardy_p
 decide_boundaries (in_node, node);
 decide_boundaries (out_node, node);
}

static void
insert_polygon (BSP_TREE * bsp, BSP_POLYGON_NODE * node)
{
 if (*bsp == NULL)
   *bsp = (BSP_NODE *) node;
 else
   {
     BSP_POLYGON_NODE *bsp_node = (BSP_POLYGON_NODE *) * bsp;
     int side = polygon_side_of_plane (node->polygon, bsp_node->plane);
     if (side & (S_IN | S_ON))
       insert_polygon (&bsp_node->in, node);
     else if (side == S_OUT)
       insert_polygon (&bsp_node->out, node);
     else
       {
         BSP_POLYGON_NODE *in_node = new_bsp_polygon_node (node->attr);
         BSP_POLYGON_NODE *out_node = new_bsp_polygon_node (node->attr);
         split_polygon_with_plane (node, bsp_node->plane, in_node, out_node);
         insert_polygon (&bsp_node->in, in_node);
         insert_polygon (&bsp_node->out, out_node);
         delete_bsp_polygon_node (node);
       }
   }
}

void
add_polygon_to_bsp (BSP_TREE * bsp, POLYGON_3D * polygon, void *attr)
{
 BSP_POLYGON_NODE *node = new_bsp_polygon_node (attr);
 init_bsp_polygon_node (node, polygon);
 insert_polygon (bsp, node);
}

static struct
{
 BSP_NODE_FUNC func;
 void *env;
}
traverse_closure[1];

static void
walk_bsp (BSP_NODE * bsp)
{
 if (bsp == NULL)
   return;
 if (bsp->tag == BSP_POLYGON)
   {
     BSP_POLYGON_NODE *node = (BSP_POLYGON_NODE *) bsp;
     if (node->plane->n[Z] < 0)
       {
         walk_bsp (node->out);
         traverse_closure->func (bsp, traverse_closure->env);
         walk_bsp (node->on);
         walk_bsp (node->in);
       }
     else
       {
         walk_bsp (node->in);
         traverse_closure->func (bsp, traverse_closure->env);
         walk_bsp (node->on);
         walk_bsp (node->out);
       }
   }
 else
   {                           // BSP_POLYLINE
     BSP_POLYLINE_NODE *node = (BSP_POLYLINE_NODE *) bsp;
     traverse_closure->func (bsp, traverse_closure->env);
     walk_bsp ((BSP_NODE *) node->in);
   }
}

void
traverse_bsp (BSP_NODE * bsp, BSP_NODE_FUNC func, void *env)
{
 traverse_closure->func = func;
 traverse_closure->env = env;
 walk_bsp (bsp);
}

void
traverse_depth_sort (BSP_NODE * bsp, BSP_NODE_FUNC func, void *env)
{
 traverse_closure->func = func;
 traverse_closure->env = env;
 for (; bsp; bsp = bsp->next)
   walk_bsp (bsp);
}

static void
indent (FILE * f, int n)
{
 while (n-- > 0)
   fprintf (f, " ");
}

static void
print_bsp_internal (FILE * f, BSP_NODE * bsp, int n)
{
 if (bsp == NULL)
   return;

 indent (f, 2 * n);
 if (bsp->tag == BSP_POLYGON)
   {

     BSP_POLYGON_NODE *node = (BSP_POLYGON_NODE *) bsp;
     fprintf (f, "BSPpolygon\n");

     indent (f, 2 * n + 1);
     print_plane (f, node->plane);

     indent (f, 2 * n + 1);
     print_polygon_3d (f, node->polygon);

     indent (f, 2 * n + 1);
     fprintf (f, "in\n");
     print_bsp_internal (f, node->in, n + 1);

     indent (f, 2 * n + 1);
     fprintf (f, "on\n");
     print_bsp_internal (f, node->on, n + 1);

     indent (f, 2 * n + 1);
     fprintf (f, "out\n");
     print_bsp_internal (f, node->out, n + 1);
   }
 else
   {                           // BSP_POLYLINE
     BSP_POLYLINE_NODE *node = (BSP_POLYLINE_NODE *) bsp;
     fprintf (f, "BSPpolyline ");
     print_polyline_3d (f, node->polyline);
     print_bsp_internal (f, (BSP_NODE *) node->in, n);
   }
}

void
print_bsp (FILE * f, BSP_NODE * bsp)
{
 print_bsp_internal (f, bsp, 0);
}

void
add_polygon_to_sort (BSP_TREE * bsp, POLYGON_3D * polygon, void *attr)
{
 BSP_POLYGON_NODE *node = new_bsp_polygon_node (attr);
 init_bsp_polygon_node (node, polygon);
 node->next = *bsp;
 *bsp = (BSP_NODE *) node;
}

void
add_polyline_to_sort (BSP_TREE * bsp, POLYLINE_3D * polyline, void *attr)
{
 BSP_POLYLINE_NODE *node = new_bsp_polyline_node (attr);
 init_bsp_polyline (node, polyline);
 node->next = *bsp;
 *bsp = (BSP_NODE *) node;
}

// quicksort of linked list
#define FAR_DEPTH(Node) (-(Node)->extent->min[Z])
#define NEAR_DEPTH(Node) (-(Node)->extent->max[Z])

// leq provides ascending sort, so reverse to get max depth at head of list
#define LEQ(A,B) (FAR_DEPTH(A) >= FAR_DEPTH(B))

static void
qs (BSP_NODE * hd, BSP_NODE * tl, BSP_NODE ** rtn)
{
 int nlo, nhi;
 BSP_NODE *lo, *hi, *q, *p;

 /* Invariant:  Return head sorted with `tl' appended. */
 while (hd != NULL)
   {

     nlo = nhi = 0;
     lo = hi = NULL;
     q = hd;
     p = hd->next;

     /* Reverse ascending prefix onto hi.  This gives
        O(n) behavior on sorted and reverse-sorted inputs. */
     while (p != NULL && LEQ (p, hd))
       {
         hd->next = hi;
         hi = hd;
         ++nhi;
         hd = p;
         p = p->next;
       }

     /* If entire list was ascending, we're done. */
     if (p == NULL)
       {
         *rtn = hd;
         hd->next = hi;
         q->next = tl;
         return;
       }

     /* Partition and count sizes. */
     while (p != NULL)
       {
         q = p->next;
         if (LEQ (p, hd))
           {
             p->next = lo;
             lo = p;
             ++nlo;
           }
         else
           {
             p->next = hi;
             hi = p;
             ++nhi;
           }
         p = q;
       }

     /* Recur to establish invariant for sublists of hd,
        choosing shortest list first to limit stack. */
     if (nlo < nhi)
       {
         qs (lo, hd, rtn);
         rtn = &hd->next;
         hd = hi;              /* Eliminated tail-recursive call. */
       }
     else
       {
         qs (hi, tl, &hd->next);
         tl = hd;
         hd = lo;              /* Eliminated tail-recursive call. */
       }
   }
 /* Base case of recurrence. Invariant is easy here. */
 *rtn = tl;
}

static int
xy_intersect_p (BOX_3D * a, BOX_3D * b)
{
 if (a->max[X] < b->min[X])    // a left of b
   return 0;
 if (a->min[X] > b->max[X])    // a right of b
   return 0;
 if (a->max[Y] < b->min[Y])    // a below  b
   return 0;
 if (a->min[Y] > b->max[Y])    // a above b
   return 0;
 return 1;
}

#define SHORTEST_ALLOWABLE_SIDE 1e-4

static void
make_polygon_projection (POLYGON_2D * projection,
                        BSP_POLYGON_NODE * polygon_node)
{
 int i, j;

 clear_polygon_2d (projection);
 if (polygon_node->plane->n[Z] >= 0)
   {
     for (i = 0, j = polygon_node->polygon->n_sides - 1;
          i < polygon_node->polygon->n_sides; j = i++)
       {
         if (dist_2d
             (polygon_node->polygon->v[i],
              polygon_node->polygon->v[j]) > SHORTEST_ALLOWABLE_SIDE)
           copy_pt_2d (pushed_polygon_2d_v (projection),
                       polygon_node->polygon->v[i]);
       }
   }
 else
   {
     for (i = polygon_node->polygon->n_sides - 1, j = 0; i >= 0; j = i--)
       {
         if (dist_2d
             (polygon_node->polygon->v[i],
              polygon_node->polygon->v[j]) > SHORTEST_ALLOWABLE_SIDE)
           copy_pt_2d (pushed_polygon_2d_v (projection),
                       polygon_node->polygon->v[i]);
       }
   }
}

#define OVERLAP_EPS 1e-3

int
projections_overlap_p (BSP_POLYGON_NODE * p, BSP_POLYGON_NODE * q)
{
 int r;
 POLYGON_2D p_projection[1], q_projection[1], cso[1];

 init_polygon_2d (p_projection);
 init_polygon_2d (q_projection);
 init_polygon_2d (cso);

 make_polygon_projection (p_projection, p);
 make_polygon_projection (q_projection, q);
 if (p_projection->n_sides < 2 && q_projection->n_sides < 2)
   {
     r = 0;
   }
 else if (p_projection->n_sides < 2)
   {
     r = point_near_convex_polygon_2d_p (p->polygon->v[0], q_projection,
                                         OVERLAP_EPS);
   }
 else if (q_projection->n_sides < 2)
   {
     r = point_near_convex_polygon_2d_p (q->polygon->v[0], p_projection,
                                         OVERLAP_EPS);
   }
 else
   {
     make_cso_polygon_2d (cso, p_projection, origin_2d, q_projection);
     r = point_near_convex_polygon_2d_p (origin_2d, cso, OVERLAP_EPS);
   }

 clear_polygon_2d (p_projection);
 clear_polygon_2d (q_projection);
 clear_polygon_2d (cso);
 return r;
}

#define OVERLAP_TOLERANCE 1e-3

int
polyline_projection_overlaps_polygon (BSP_POLYLINE_NODE * polyline_node,
                                     BSP_POLYGON_NODE * polygon_node)
{
 int i, r;
 POLYGON_2D polygon_projection[1], line_seg_projection[1], cso[1];

 init_polygon_2d (polygon_projection);
 init_polygon_2d (line_seg_projection);
 init_polygon_2d (cso);

 make_polygon_projection (polygon_projection, polygon_node);
 if (polygon_projection->n_sides < 2)
   {
     // a point can't obscure a line
     r = 0;
   }
 else if (polyline_node->polyline->n_vertices == 1)
   {
     // polyline is single vertex; just check to see if it lies in projection
     r = point_near_convex_polygon_2d_p (polyline_node->polyline->v[0],
                                         polygon_projection,
                                         OVERLAP_TOLERANCE);
   }
 else
   {

     // use a two-sided polygon to model each edge;
     setup_polygon_2d (line_seg_projection, 2);
     line_seg_projection->n_sides = 2;
     r = 0;
     for (i = 0; i < polyline_node->polyline->n_vertices - 1; i++)
       {
         copy_pt_2d (line_seg_projection->v[0],
                     polyline_node->polyline->v[i]);
         copy_pt_2d (line_seg_projection->v[1],
                     polyline_node->polyline->v[i + 1]);
         make_cso_polygon_2d (cso, line_seg_projection, origin_2d,
                              polygon_projection);
         r |= point_near_convex_polygon_2d_p (origin_2d, cso,
                                              OVERLAP_TOLERANCE);
         if (r)
           break;
       }
   }
 clear_polygon_2d (polygon_projection);
 clear_polygon_2d (line_seg_projection);
 clear_polygon_2d (cso);
 return r;
}

static void
debug_print (BSP_NODE * p)
{
 fprintf (stderr, "\nlist:\n");
 while (p)
   {
     fprintf (stderr, "  %p:%sprev=%p near=%.4g far=%.4g\n",
              p,
              p->mark ? "*" : " ", p->prev, NEAR_DEPTH (p), FAR_DEPTH (p));
     p = p->next;
   }
}

typedef struct make_list_of_bsp_env_t
{
 BSP_NODE *head, *tail;
 int n;
}
MAKE_LIST_OF_BSP_ENV;

static void
make_list_of_bsp (BSP_NODE * bsp, void *v_env)
{
 MAKE_LIST_OF_BSP_ENV *env = (MAKE_LIST_OF_BSP_ENV *) v_env;
 if (env->tail)
   {
     env->tail->next = bsp;
     bsp->prev = env->tail;
     bsp->next = NULL;
     env->tail = bsp;
   }
 else
   {
     env->head = env->tail = bsp;
   }
 ++env->n;
}

// check invariants in the depth sort list
static void
check_consistency (BSP_TREE hd)
{
 int n_marks, n_other;
 BSP_NODE *p, *q;

 n_marks = 0;
 for (q = NULL, p = hd; p && p->mark; q = p, p = p->next)
   {
     n_marks++;
     if (p->prev != q)
       {
         debug_print (hd);
         die (no_line, "broken prev pointer @ %d (%p != %p)", n_marks,
              p->prev, q);
       }
     if (p->extent->min[X] == 0 && p->extent->max[X] == 0 &&
         p->extent->min[Y] == 0 && p->extent->max[Y] == 0 &&
         p->extent->min[Z] == 0 && p->extent->max[Z] == 0)
       die (no_line, "unset extent");
   }

 n_other = 0;
 for (; p; q = p, p = p->next)
   {
     n_other++;
     if (p->mark)
       die (no_line, "unexpected mark");
     if (p->prev != q)
       {
         debug_print (hd);
         die (no_line, "broken prev pointer @ %d (%p != %p)",
              n_marks + n_other, p->prev, q);
       }
     if (p->extent->min[X] > p->extent->max[X])
       die (no_line, "unset extent");
     if (q && !q->mark && FAR_DEPTH (p) > FAR_DEPTH (q))
       {
         debug_print (hd);
         die (no_line, "far depth out of order @ %d", n_marks + n_other);
       }
   }
}

void
insert_by_depth (BSP_TREE * hd, BSP_NODE * node)
{
 BSP_NODE *p, *q;

 // place p after insert point and q before
 for (q = NULL, p = *hd;
      p && (p->mark || FAR_DEPTH (p) > FAR_DEPTH (node)); q = p, p = p->next)
   /* skip */ ;

 // insert
 node->prev = q;
 node->next = p;
 if (q)
   q->next = node;
 else
   *hd = node;
 if (p)
   p->prev = node;
}

// this is taken almost directly from Newell's 1972 paper except that
// a BSP is used to resolve intersections and cyclic overlaps and it
// incorporates polyline objects
void
sort_by_depth (BSP_TREE * bsp)
{
 int side,
   n_probes, n_swaps, n_nodes,
   n_bsps, n_in, n_out, n_ppos, n_plos, n_bsp_in_nodes, n_bsp_out_nodes;
 BSP_NODE *p, *p_next, *q, *prev, *t, *t_next, *r;
 BSP_POLYGON_NODE *polygon_node;
 BSP_POLYLINE_NODE *polyline_node;
 PLANE *plane;
 BSP_TREE sub_bsp;
 MAKE_LIST_OF_BSP_ENV env[1];

 // quicksort on deepest vertex, back-to-front
 qs (*bsp, NULL, &p);

 // hook up prev pointers and make sure marks are clear
 n_nodes = 0;
 for (prev = NULL, q = p; q; prev = q, q = q->next)
   {
     q->prev = prev;
     q->mark = NULL;
     ++n_nodes;
   }

 // keep some stats just for fun
 n_probes = n_swaps = n_bsps = n_bsp_in_nodes = n_bsp_out_nodes =
   n_ppos = n_plos = 0;

 // debug_print(p);

 // this is now output list
 r = NULL;

 // goto here whenever the current check fails
 // for "p", the hopeful deepest polygon
restart_overlap_check:

 while (p)
   {

     if (n_nodes < 1000)
       check_consistency (p);

     // check overlapping objects for necessary swaps.
     for (q = p->next;
          q && (q->mark || FAR_DEPTH (q) > NEAR_DEPTH (p)); q = q->next)
       {

         ++n_probes;

         // rectangular x-y extents don't overlap, so a moo point (utterly meaningless)
         if (!xy_intersect_p (p->extent, q->extent))
           continue;

         // two polylines don't matter
         // DEBUG: it really does if they're different colors
         if (p->tag == BSP_POLYLINE && q->tag == BSP_POLYLINE)
           continue;

         // two polygons
         if (p->tag == BSP_POLYGON && q->tag == BSP_POLYGON)
           {

             // p is contained wholly in the back halfspace of q
             polygon_node = (BSP_POLYGON_NODE *) p;
             plane = ((BSP_POLYGON_NODE *) q)->plane;
             side = polygon_side_of_plane (polygon_node->polygon, plane);
             if (side == S_ON ||
                 (plane->n[Z] >= 0 && side == S_IN) ||
                 (plane->n[Z] <= 0 && side == S_OUT))
               continue;

             // q is contained wholly in the front halfspace of p
             polygon_node = (BSP_POLYGON_NODE *) q;
             plane = ((BSP_POLYGON_NODE *) p)->plane;
             side = polygon_side_of_plane (polygon_node->polygon, plane);
             if (side == S_ON ||
                 (plane->n[Z] >= 0 && side == S_OUT) ||
                 (plane->n[Z] <= 0 && side == S_IN))
               continue;

             // projections do not overlap
             ++n_ppos;
             if (!projections_overlap_p
                 ((BSP_POLYGON_NODE *) p, (BSP_POLYGON_NODE *) q))
               continue;
           }

         if (p->tag == BSP_POLYLINE)
           {                   // and q is a polygon
             polyline_node = (BSP_POLYLINE_NODE *) p;
             plane = ((BSP_POLYGON_NODE *) q)->plane;
             side = polyline_side_of_plane (polyline_node->polyline, plane);

             // line is contained wholly in the back halfspace of plane
             // lines lying on plane should be swapped so plane is drawn first
             if ((plane->n[Z] >= 0 && side == S_IN) ||
                 (plane->n[Z] <= 0 && side == S_OUT))
               continue;

             // projections do not overlap
             ++n_plos;
             if (!polyline_projection_overlaps_polygon
                 (polyline_node, (BSP_POLYGON_NODE *) q))
               continue;
           }

         if (q->tag == BSP_POLYLINE)
           {                   // and p is a polygon
             polyline_node = (BSP_POLYLINE_NODE *) q;
             plane = ((BSP_POLYGON_NODE *) p)->plane;
             side = polyline_side_of_plane (polyline_node->polyline, plane);

             // line is on or contained wholly in the front halfspace of the plane
             // a line lying on the plane can stay where it is
             if (side == S_ON ||
                 (plane->n[Z] >= 0 && side == S_OUT) ||
                 (plane->n[Z] <= 0 && side == S_IN))
               continue;

             // projections do not overlap
             ++n_plos;
             if (!polyline_projection_overlaps_polygon
                 (polyline_node, (BSP_POLYGON_NODE *) p))
               continue;
           }

         if (q->mark)
           {

             // we've discovered an intersection or cyclic overlap; break it by
             // putting all the marked nodes in a bsp, then pulling them
             // out and inserting them back on the list; remember our bsps
             // need all polygons inserted before all polylines

             ++n_bsps;
             sub_bsp = NULL;
             n_in = 0;
             t = NULL;         // use t to hold polylines temporarily
             while (p && p->mark)
               {
                 p_next = p->next;

                 if (p->tag == BSP_POLYGON)
                   {
                     p->next = p->prev = NULL;
                     insert_polygon (&sub_bsp, (BSP_POLYGON_NODE *) p);
                   }
                 else
                   {           // save polyline temporarily
                     p->next = t;
                     t = p;
                   }
                 ++n_in;
                 p = p_next;
                 if (p)
                   p->prev = NULL;
               }
             // insert the polylines now that all polygons are complete
             while (t)
               {
                 t_next = t->next;
                 t->next = t->prev = NULL;
                 insert_polyline (&sub_bsp, (BSP_POLYLINE_NODE *) t);
                 t = t_next;
               }

             // now traverse the bsp to get the objects back out, including split ones
             env->n = 0;
             env->head = env->tail = NULL;
             traverse_bsp (sub_bsp, make_list_of_bsp, env);
             n_out = env->n;

             // splitting should always increase the number of primitives, but
             // polygons very close in depth can cause split to fail; just shovel
             // the result polygons to the output with a warning.
             if (n_out <= n_in)
               {
                 warn (no_line, "split failed in=%d, out=%d", n_in, n_out);
                 remark (no_line,
                         "if hidden surfaces are wrong, try -b option");
                 for (t = env->head; t; t = t_next)
                   {
                     t_next = t->next;
                     t->next = r;
                     t->in = t->out = t->on = t->prev = t->mark = NULL;
                     r = t;
                   }
                 goto restart_overlap_check;
               }
             // re-insert in the sorted list
             for (t = env->head; t; t = t_next)
               {
                 t_next = t->next;
                 t->in = t->out = t->on = t->prev = t->next = t->mark = NULL;
                 insert_by_depth (&p, t);
               }

             n_bsp_in_nodes += n_in;
             n_bsp_out_nodes += n_out;

             goto restart_overlap_check;
           }
         else
           {

             // no overlap, so pull q forward to head of list

             // unlink q
             if (q->next)
               q->next->prev = q->prev;
             q->prev->next = q->next;

             // mark
             q->mark = p;

             // push
             q->prev = NULL;

             q->next = p;
             p->prev = q;
             p = q;

             ++n_swaps;

             goto restart_overlap_check;
           }
       }

     // overlap check saw no problems; pop head onto return list
     p_next = p->next;
     if (p_next)
       p_next->prev = NULL;

     // push on output
     p->next = r;
     p->prev = NULL;
     r = p;

     // move to next item
     p = p_next;
   }
 // pop from q and push onto q until q is empty
 q = r;
 r = NULL;
 while (q)
   {
     t = q;
     q = q->next;              // pop
     t->next = r;
     r = t;                    // push
   }

 {
   int n_probes_possible = n_nodes + n_bsp_out_nodes - n_bsp_in_nodes;

   remark (no_line,
           "node=%d probe=%.1lf swap=%d split=%d (in=%d out=%d) ols=%d/%d",
           n_nodes,
           (n_probes_possible >=
            0) ? (double) n_probes / n_probes_possible : 0.0, n_swaps,
           n_bsps, n_bsp_in_nodes, n_bsp_out_nodes, n_ppos, n_plos);
 }

 *bsp = r;
}