/* 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;
}
