/* scene.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 <math.h>
#include "scene.h"
#include "emit.h"

DECLARE_DYNAMIC_2D_ARRAY_FUNCS (POINT_LIST_3D, POINT_3D, FLOAT,
                               point_list_3d, v, n_pts, NO_OTHER_INIT)
DECLARE_DYNAMIC_2D_ARRAY_FUNCS (TRANSFORM_LIST, TRANSFORM, FLOAT,
                               transform_list, xf, n_xfs, NO_OTHER_INIT)
// this must match the definition of OBJECT_TYPE
    char *object_type_str[] = {
      "base",
      "tag",
      "option list",
      "scalar",
      "point",
      "vector",
      "transform",
      "dots",
      "line",
      "curve",
      "polygon",
      "special",
      "sweep",
      "repeat",
      "compound",
    };

#define LAY_IN  0
#define LAY_OVER 1
#define LAY_UNDER -1

int lay_val (OPTS * opts, int lay_default)
{
 char *val = opt_val (opts, "lay");
 if (!val)
   return lay_default;
 if (strcmp (val, "over") == 0)
   return LAY_OVER;
 else if (strcmp (val, "under") == 0)
   return LAY_UNDER;
 else if (strcmp (val, "in") == 0)
   return LAY_IN;
 else
   {
     warn (no_line, "lay=%s has been ignored", val);
     return lay_default;
   }
}

OBJECT *
new_tag_def (void)
{
 TAG_DEF *r = safe_malloc (sizeof *r);
 r->tag = O_TAG_DEF;
 r->sibling = NULL;
 return (OBJECT *) r;
}

OBJECT *
new_opts_def (char *opts_str, SRC_LINE line)
{
 OPTS_DEF *r = safe_malloc (sizeof *r);
 r->tag = O_OPTS_DEF;
 r->sibling = NULL;
 r->opts = new_opts (opts_str, line);
 return (OBJECT *) r;
}

OBJECT *
new_scalar_def (FLOAT val)
{
 SCALAR_DEF *r = safe_malloc (sizeof *r);
 r->tag = O_SCALAR_DEF;
 r->sibling = NULL;
 r->val = val;
 return (OBJECT *) r;
}

OBJECT *
new_point_def (POINT_3D p)
{
 POINT_DEF *r = safe_malloc (sizeof *r);
 r->tag = O_POINT_DEF;
 r->sibling = NULL;
 copy_pt_3d (r->p, p);
 return (OBJECT *) r;
}

OBJECT *
new_vector_def (VECTOR_3D v)
{
 VECTOR_DEF *r = safe_malloc (sizeof *r);
 r->tag = O_VECTOR_DEF;
 r->sibling = NULL;
 copy_vec_3d (r->v, v);
 return (OBJECT *) r;
}

OBJECT *
new_transform_def (TRANSFORM xf)
{
 TRANSFORM_DEF *r = safe_malloc (sizeof *r);
 r->tag = O_TRANSFORM_DEF;
 r->sibling = NULL;
 copy_transform (r->xf, xf);
 return (OBJECT *) r;
}

void
translate_points (POINT_LIST_3D * dst, OBJECT * src_obj)
{
 POINT_DEF *sibling, *src = (POINT_DEF *) src_obj;

 while (src)
   {
     copy_pt_3d (pushed_point_list_3d_v (dst), src->p);
     sibling = (POINT_DEF *) src->sibling;
     safe_free (src);
     src = sibling;
   }
}

DOTS_OBJECT *
raw_dots (OPTS * opts)
{
 DOTS_OBJECT *r = safe_malloc (sizeof *r);
 r->tag = O_DOTS;
 r->sibling = NULL;
 r->opts = opts;
 init_point_list_3d (r->pts);
 return r;
}

OBJECT *
new_dots (OPTS * opts, OBJECT * pts)
{
 DOTS_OBJECT *r = raw_dots (opts);
 translate_points (r->pts, pts);
 return (OBJECT *) r;
}

OBJECT *
copy_dots (OBJECT * obj)
{
 DOTS_OBJECT *org = (DOTS_OBJECT *) obj, *r = raw_dots (org->opts);
 copy_point_list_3d (r->pts, org->pts);
 return (OBJECT *) r;
}

LINE_OBJECT *
raw_line (OPTS * opts)
{
 LINE_OBJECT *r = safe_malloc (sizeof *r);
 r->tag = O_LINE;
 r->sibling = NULL;
 r->opts = opts;
 init_point_list_3d (r->pts);
 return r;
}

OBJECT *
new_line (OPTS * opts, OBJECT * pts)
{
 LINE_OBJECT *r = raw_line (opts);
 translate_points (r->pts, pts);
 return (OBJECT *) r;
}

OBJECT *
copy_line (OBJECT * obj)
{
 LINE_OBJECT *org = (LINE_OBJECT *) obj, *r = raw_line (org->opts);
 copy_point_list_3d (r->pts, org->pts);
 return (OBJECT *) r;
}

CURVE_OBJECT *
raw_curve (OPTS * opts)
{
 CURVE_OBJECT *r = safe_malloc (sizeof *r);
 r->tag = O_CURVE;
 r->sibling = NULL;
 r->opts = opts;
 init_point_list_3d (r->pts);
 return r;
}

OBJECT *
new_curve (OPTS * opts, OBJECT * pts)
{
 CURVE_OBJECT *r = raw_curve (opts);
 translate_points (r->pts, pts);
 return (OBJECT *) r;
}

OBJECT *
copy_curve (OBJECT * obj)
{
 CURVE_OBJECT *org = (CURVE_OBJECT *) obj, *r = raw_curve (org->opts);
 copy_point_list_3d (r->pts, org->pts);
 return (OBJECT *) r;
}

POLYGON_OBJECT *
raw_polygon (OPTS * opts)
{
 POLYGON_OBJECT *r = safe_malloc (sizeof *r);
 r->tag = O_POLYGON;
 r->sibling = NULL;
 r->opts = opts;
 init_point_list_3d (r->pts);
 r->border_p = 0;
 return r;
}

OBJECT *
new_polygon (OPTS * opts, OBJECT * pts)
{
 POLYGON_OBJECT *r = raw_polygon (opts);
 translate_points (r->pts, pts);
 return (OBJECT *) r;
}

OBJECT *
copy_polygon (OBJECT * obj)
{
 POLYGON_OBJECT *org = (POLYGON_OBJECT *) obj, *r = raw_polygon (org->opts);
 copy_point_list_3d (r->pts, org->pts);
 return (OBJECT *) r;
}

static SPECIAL_OBJECT *
raw_special (OPTS * opts)
{
 SPECIAL_OBJECT *r = safe_malloc (sizeof *r);
 r->tag = O_SPECIAL;
 r->sibling = NULL;
 r->code = NULL;
 r->opts = opts;
 init_point_list_3d (r->pts);
 return r;
}

OBJECT *
new_special (char *code, OPTS * opts, OBJECT * pts, SRC_LINE line)
{
 SPECIAL_OBJECT *r = raw_special (opts);
 r->code = code;
 translate_points (r->pts, pts);
 // syntax check
 process_special (NULL, r, line);
 return (OBJECT *) r;
}

OBJECT *
copy_special (OBJECT * obj)
{
 SPECIAL_OBJECT *org = (SPECIAL_OBJECT *) obj, *r = raw_special (org->opts);
 copy_point_list_3d (r->pts, org->pts);
 r->code = safe_strdup (org->code);
 return (OBJECT *) r;
}

SWEEP_OBJECT *
raw_sweep (OPTS * opts)
{
 SWEEP_OBJECT *r = safe_malloc (sizeof *r);
 r->tag = O_SWEEP;
 r->sibling = NULL;
 r->n_slices = 0;
 r->closed_p = 0;
 init_transform_list (r->xforms);
 r->opts = opts;
 r->swept = NULL;
 return r;
}

void
translate_transforms (TRANSFORM_LIST * dst, OBJECT * src_obj)
{
 TRANSFORM_DEF *sibling, *src = (TRANSFORM_DEF *) src_obj;
 while (src)
   {
     copy_transform (pushed_transform_list_xf (dst), src->xf);
     sibling = (TRANSFORM_DEF *) src->sibling;
     safe_free (src);
     src = sibling;
   }
}

OBJECT *
new_sweep (OPTS * opts, int n_slices, int closed_p, OBJECT * xfs,
          OBJECT * swept)
{
 SWEEP_OBJECT *r = raw_sweep (opts);
 r->n_slices = n_slices;
 r->closed_p = closed_p;
 translate_transforms (r->xforms, xfs);
 r->swept = swept;
 return (OBJECT *) r;
}

// this is a shallow copy
OBJECT *
copy_sweep (OBJECT * obj)
{
 SWEEP_OBJECT *org = (SWEEP_OBJECT *) obj, *r = raw_sweep (org->opts);
 r->n_slices = org->n_slices;
 r->closed_p = org->closed_p;
 copy_transform_list (r->xforms, org->xforms);
 r->swept = org->swept;
 return (OBJECT *) r;
}

REPEAT_OBJECT *
raw_repeat (void)
{
 REPEAT_OBJECT *r = safe_malloc (sizeof *r);
 r->tag = O_REPEAT;
 r->sibling = NULL;
 r->n = 0;
 init_transform_list (r->xforms);
 r->repeated = NULL;
 return r;
}

OBJECT *
new_repeat (int n, OBJECT * xfs, OBJECT * repeated)
{
 REPEAT_OBJECT *r = raw_repeat ();
 r->n = n;
 translate_transforms (r->xforms, xfs);
 r->repeated = repeated;
 return (OBJECT *) r;
}

OBJECT *
copy_repeat (OBJECT * obj)
{
 REPEAT_OBJECT *org = (REPEAT_OBJECT *) obj, *r = raw_repeat ();
 r->n = org->n;
 copy_transform_list (r->xforms, org->xforms);
 r->repeated = org->repeated;  // shallow copy
 return (OBJECT *) r;
}

OBJECT *
new_compound (TRANSFORM xform, OBJECT * child)
{
 COMPOUND_OBJECT *r = safe_malloc (sizeof *r);
 r->tag = O_COMPOUND;
 r->sibling = NULL;
 copy_transform (r->xform, xform);
 r->child = child;
 return (OBJECT *) r;
}

// this is a shallow copy
OBJECT *
copy_compound (OBJECT * obj)
{
 COMPOUND_OBJECT *org = (COMPOUND_OBJECT *) obj;
 return new_compound (org->xform, org->child);
}

typedef OBJECT *(*COPY_FUNC) (OBJECT *);

static COPY_FUNC copy_tbl[] = {
 NULL,                         // O_BASE
 NULL,                         // O_TAG_DEF
 NULL,                         // O_OPTS_DEF
 NULL,                         // O_SCALAR_DEF
 NULL,                         // O_POINT_DEF
 NULL,                         // O_VECTOR_DEF
 NULL,                         // O_TRANSFORM_DEF
 copy_dots,
 copy_line,
 copy_curve,
 copy_polygon,
 copy_special,
 copy_sweep,
 copy_repeat,
 copy_compound,
};

OBJECT *
copy_drawable (OBJECT * obj)
{
 OBJECT *r = NULL;
 while (obj)
   {
     if (copy_tbl[obj->tag])
       {
         OBJECT *copy = (*copy_tbl[obj->tag]) (obj);
         copy->sibling = r;
         r = copy;
       }
     else
       {
         die (no_line, "copy_drawable: attempt to copy non-drawable %s",
              object_type_str[obj->tag]);
       }
     obj = obj->sibling;
   }
 return sibling_reverse (r);
}

OBJECT *
sibling_reverse (OBJECT * obj)
{
 OBJECT *p, *q, *t;

 // pop from p and push onto q until p is empty
 p = obj;
 q = NULL;
 while (p)
   {
     t = p;
     p = p->sibling;           // pop
     t->sibling = q;
     q = t;                    // push
   }
 return q;
}

OBJECT *
object_from_expr (EXPR_VAL * val)
{
 switch (val->tag)
   {
   case E_FLOAT:
     return new_scalar_def (val->val.flt);
   case E_POINT:
     return new_point_def (val->val.pt);
   case E_VECTOR:
     return new_vector_def (val->val.vec);
   case E_TRANSFORM:
     return new_transform_def (val->val.xf);
   default:
     die (no_line, "object_from_expr: unknown value tag %d", val->tag);
   }
 return NULL;                  // never occurs
}

void
transform_points (POINT_LIST_3D * dst_pts, TRANSFORM xf,
                 POINT_LIST_3D * src_pts)
{
 int i;

 setup_point_list_3d (dst_pts, src_pts->n_pts);
 for (i = 0; i < src_pts->n_pts; i++)
   transform_pt_3d (dst_pts->v[i], xf, src_pts->v[i]);
 dst_pts->n_pts = src_pts->n_pts;
}

static void
fill_transform_accum (TRANSFORM_LIST * accum, TRANSFORM_LIST * inc)
{
 int i;

 setup_transform_list (accum, inc->n_xfs);
 accum->n_xfs = inc->n_xfs;
 for (i = 0; i < inc->n_xfs; i++)
   set_ident (accum->xf[i]);
}

static void
advance_transform_accum (TRANSFORM_LIST * accum, TRANSFORM_LIST * inc)
{
 int i;
 for (i = 0; i < accum->n_xfs; i++)
   compose (accum->xf[i], accum->xf[i], inc->xf[i]);
}

static void
compose_transform_accum (TRANSFORM xf, TRANSFORM_LIST * accum,
                        TRANSFORM model_view_xf)
{
 int i;

 if (accum->n_xfs <= 0)
   die (no_line, "zero size accumulator");
 copy_transform (xf, accum->xf[0]);
 // left-multiply because accumulator is in "then" order
 for (i = 1; i < accum->n_xfs; i++)
   compose (xf, accum->xf[i], xf);
 if (model_view_xf)
   compose (xf, model_view_xf, xf);
}

OBJECT *
flat_dots (OBJECT * obj, TRANSFORM xf)
{
 DOTS_OBJECT *s = (DOTS_OBJECT *) obj, *dots = raw_dots (s->opts);
 transform_points (dots->pts, xf, s->pts);
 return (OBJECT *) dots;
}

OBJECT *
flat_line (OBJECT * obj, TRANSFORM xf)
{
 LINE_OBJECT *s = (LINE_OBJECT *) obj, *line = raw_line (s->opts);
 check_opts (s->opts, OPT_INTERNAL | OPT_LINE,
             "unknown line option %s=%s will be ignored",
             global_env->output_language, no_line);
 transform_points (line->pts, xf, s->pts);
 return (OBJECT *) line;
}

OBJECT *
flat_curve (OBJECT * obj, TRANSFORM xf)
{
 CURVE_OBJECT *s = (CURVE_OBJECT *) obj, *curve = raw_curve (s->opts);
 transform_points (curve->pts, xf, s->pts);
 return (OBJECT *) curve;
}

OBJECT *
flat_polygon (OBJECT * obj, TRANSFORM xf)
{
 POLYGON_OBJECT *s = (POLYGON_OBJECT *) obj,
   *polygon = raw_polygon (s->opts);
 check_opts (s->opts, OPT_INTERNAL | OPT_POLYGON | OPT_LINE,
             "unknown polygon option %s=%s will be ignored",
             global_env->output_language, no_line);
 transform_points (polygon->pts, xf, s->pts);
 return (OBJECT *) polygon;
}

OBJECT *
flat_special (OBJECT * obj, TRANSFORM xf)
{
 SPECIAL_OBJECT *s = (SPECIAL_OBJECT *) obj,
   *special = raw_special (s->opts);
 special->code = safe_strdup (s->code);
 transform_points (special->pts, xf, s->pts);
 return (OBJECT *) special;
}

#define MAX_WARP  1e-5

// return -1 if no split is necessary
// return 0 if best spilt is on the 0--2 line
// return 1 if best split is on the 1--3 line
static int
best_triangle_split (POINT_3D v0, POINT_3D v1, POINT_3D v2, POINT_3D v3)
{
 VECTOR_3D n, d0, d1, e, e_max;
 FLOAT e_len_sqr, e_max_len_sqr, warp;

 sub_vecs_3d (d0, v2, v0);
 sub_vecs_3d (d1, v3, v1);
 cross (n, d0, d1);

 // if the cross product is zero length, the polygon is degenerate and can
 // be considered flat; no need to traingulate
 if (!find_unit_vec_3d (n, n))
   return -1;

 // find the edge of maximum length; probably not necessary
 sub_vecs_3d (e_max, v1, v0);
 e_max_len_sqr = dot_3d (e_max, e_max);

 sub_vecs_3d (e, v2, v1);
 e_len_sqr = dot_3d (e, e);
 if (e_len_sqr > e_max_len_sqr)
   {
     e_max_len_sqr = e_len_sqr;
     copy_vec_3d (e_max, e);
   }

 sub_vecs_3d (e, v3, v2);
 e_len_sqr = dot_3d (e, e);
 if (e_len_sqr > e_max_len_sqr)
   {
     e_max_len_sqr = e_len_sqr;
     copy_vec_3d (e_max, e);
   }

 sub_vecs_3d (e, v0, v3);
 e_len_sqr = dot_3d (e, e);
 if (e_len_sqr > e_max_len_sqr)
   {
     e_max_len_sqr = e_len_sqr;
     copy_vec_3d (e_max, e);
   }
 // flat if projection of edge on normal is small, else split on shortest diagonal
 warp = dot_3d (e_max, n);
 return
   -MAX_WARP <= warp && warp <= MAX_WARP ? -1 :
   dot_3d (d0, d0) < dot_3d (d1, d1) ? 0 : 1;
}

// add triangular or quadrilateral faces to object list depending on flatness
static void
make_faces (OBJECT ** r, OPTS * opts, TRANSFORM xf,
           POINT_3D v0, POINT_3D v1, POINT_3D v2, POINT_3D v3, int split_p)
{
 POLYGON_OBJECT *new_polygon;

 if (!split_p)
   goto no_split;

 switch (best_triangle_split (v0, v1, v2, v3))
   {
   case -1:
   no_split:
     new_polygon = raw_polygon (opts);
     setup_point_list_3d (new_polygon->pts, 4);
     transform_pt_3d (new_polygon->pts->v[0], xf, v0);
     transform_pt_3d (new_polygon->pts->v[1], xf, v1);
     transform_pt_3d (new_polygon->pts->v[2], xf, v2);
     transform_pt_3d (new_polygon->pts->v[3], xf, v3);
     new_polygon->pts->n_pts = 4;
     new_polygon->sibling = *r;
     *r = (OBJECT *) new_polygon;
     break;

   case 0:
     new_polygon = raw_polygon (opts);
     setup_point_list_3d (new_polygon->pts, 3);
     transform_pt_3d (new_polygon->pts->v[0], xf, v0);
     transform_pt_3d (new_polygon->pts->v[1], xf, v1);
     transform_pt_3d (new_polygon->pts->v[2], xf, v2);
     new_polygon->pts->n_pts = 3;
     new_polygon->sibling = *r;
     *r = (OBJECT *) new_polygon;
     new_polygon = raw_polygon (opts);
     setup_point_list_3d (new_polygon->pts, 3);
     transform_pt_3d (new_polygon->pts->v[0], xf, v2);
     transform_pt_3d (new_polygon->pts->v[1], xf, v3);
     transform_pt_3d (new_polygon->pts->v[2], xf, v0);
     new_polygon->pts->n_pts = 3;
     new_polygon->sibling = *r;
     *r = (OBJECT *) new_polygon;
     break;

   case 1:
     new_polygon = raw_polygon (opts);
     setup_point_list_3d (new_polygon->pts, 3);
     transform_pt_3d (new_polygon->pts->v[0], xf, v1);
     transform_pt_3d (new_polygon->pts->v[1], xf, v2);
     transform_pt_3d (new_polygon->pts->v[2], xf, v3);
     new_polygon->pts->n_pts = 3;
     new_polygon->sibling = *r;
     *r = (OBJECT *) new_polygon;
     new_polygon = raw_polygon (opts);
     setup_point_list_3d (new_polygon->pts, 3);
     transform_pt_3d (new_polygon->pts->v[0], xf, v3);
     transform_pt_3d (new_polygon->pts->v[1], xf, v0);
     transform_pt_3d (new_polygon->pts->v[2], xf, v1);
     new_polygon->pts->n_pts = 3;
     new_polygon->sibling = *r;
     *r = (OBJECT *) new_polygon;
     break;
   }
}

OBJECT *
flat_sweep (OBJECT * obj, TRANSFORM xf)
{
 int i, j, jj, split_p;
 POINT_LIST_3D *a, *b, *t;
 OBJECT *swept, *r;
 TRANSFORM sweep_xf;
 POINT_LIST_3D pts_1[1], pts_2[1];
 TRANSFORM_LIST sweep_accum[1];

 SWEEP_OBJECT *s = (SWEEP_OBJECT *) obj;

 init_point_list_3d (pts_1);
 init_point_list_3d (pts_2);
 init_transform_list (sweep_accum);

 split_p = bool_opt_p (s->opts, "split", 1)
   && bool_opt_p (global_env->opts, "split", 1);

 r = NULL;

#define ADD_TO_OUTPUT(O)  do { \
 (O)->sibling = r; \
 r = (OBJECT*)(O); \
} while (0)

 // handle definitions first; a point becomes a single line or a polygon
 if (s->swept->tag == O_POINT_DEF)
   {
     fill_transform_accum (sweep_accum, s->xforms);
     for (swept = s->swept; swept; swept = swept->sibling)
       {
         POINT_DEF *pd = (POINT_DEF *) swept;
         if (s->closed_p)
           {
             POLYGON_OBJECT *polygon = raw_polygon (s->opts);
             for (i = 0; i < s->n_slices; i++)
               {
                 compose_transform_accum (sweep_xf, sweep_accum, xf);
                 transform_pt_3d (pushed_point_list_3d_v (polygon->pts),
                                  sweep_xf, pd->p);
                 advance_transform_accum (sweep_accum, s->xforms);
               }
             ADD_TO_OUTPUT (polygon);
           }
         else
           {
             LINE_OBJECT *line = raw_line (s->opts);
             for (i = 0; i < s->n_slices + 1; i++)
               {
                 compose_transform_accum (sweep_xf, sweep_accum, xf);
                 transform_pt_3d (pushed_point_list_3d_v (line->pts),
                                  sweep_xf, pd->p);
                 advance_transform_accum (sweep_accum, s->xforms);
               }
             ADD_TO_OUTPUT (line);
           }
       }
   }
 else
   {

     // it's drawable; recursively flatten swept object in its own coordinates
     for (swept = flat_scene (s->swept, NULL); swept; swept = swept->sibling)
       {

         // refill with identity for each swept object
         fill_transform_accum (sweep_accum, s->xforms);

         // now the different flavors of sweep depend on what's being swept and
         // the setting of the closure tag
         if (swept->tag == O_LINE)
           {
             // a line becomes a surface represented by a sequence of 4-sided polygons
             LINE_OBJECT *line = (LINE_OBJECT *) swept;

             // a is the trail buffer and b the lead
             a = pts_1;
             b = pts_2;
             copy_point_list_3d (a, line->pts);

             if (s->closed_p)
               {
                 POLYGON_OBJECT *e1 = raw_polygon (s->opts);
                 POLYGON_OBJECT *e2 = raw_polygon (s->opts);
                 OPTS *face_opts = line->opts ? line->opts : s->opts;

                 // set up in advance; e1 is filled in forward, e2 in reverse
                 setup_point_list_3d (e1->pts, s->n_slices);
                 e1->pts->n_pts = s->n_slices;
                 setup_point_list_3d (e2->pts, s->n_slices);
                 e2->pts->n_pts = s->n_slices;
                 for (i = 0; i < s->n_slices - 1; i++)
                   {
                     advance_transform_accum (sweep_accum, s->xforms);
                     compose_transform_accum (sweep_xf, sweep_accum, 0);       // apply mv transform in make_faces
                     transform_points (b, sweep_xf, line->pts);
                     // copy first and last points for 'end caps'
                     transform_pt_3d (e1->pts->v[i], xf, b->v[b->n_pts - 1]);
                     transform_pt_3d (e2->pts->v[s->n_slices - 1 - i],
                                      xf, b->v[0]);
                     for (jj = 0, j = 1; j < a->n_pts; jj = j++)
                       make_faces (&r, face_opts, xf, b->v[jj],
                                   b->v[j], a->v[j], a->v[jj], split_p);
                     t = a;
                     a = b;
                     b = t;    // swap a and b for next pass
                   }
                 // closure: add last point of original line. first to ends, then as faces
                 transform_pt_3d (e1->pts->v[i], xf,
                                  line->pts->v[line->pts->n_pts - 1]);
                 transform_pt_3d (e2->pts->v[0], xf, line->pts->v[0]);
                 for (jj = 0, j = 1; j < a->n_pts; jj = j++)
                   make_faces (&r, face_opts, xf, line->pts->v[jj],
                               line->pts->v[j], a->v[j], a->v[jj], split_p);

                 // add ends to output
                 ADD_TO_OUTPUT (e1);
                 ADD_TO_OUTPUT (e2);
               }
             else
               {
                 for (i = 0; i < s->n_slices; i++)
                   {
                     advance_transform_accum (sweep_accum, s->xforms);
                     compose_transform_accum (sweep_xf, sweep_accum, 0);
                     transform_points (b, sweep_xf, line->pts);
                     for (jj = 0, j = 1; j < a->n_pts; jj = j++)
                       make_faces (&r, s->opts, xf, b->v[jj], b->v[j],
                                   a->v[j], a->v[jj], split_p);
                     t = a;
                     a = b;
                     b = t;    // swap a and b for next pass
                   }
               }
           }
         else if (swept->tag == O_POLYGON)
           {
             // a polygon becomes a surface represented by a sequence of 4-sided polygons (with "end caps")
             POLYGON_OBJECT *new_polygon, *polygon =
               (POLYGON_OBJECT *) swept;
             OPTS *end_opts = polygon->opts ? polygon->opts : s->opts;

             if (s->closed_p)
               warn (no_line,
                     "closure tag on polygon sweep ignored (sorry, no line number)");

             a = pts_1;
             b = pts_2;
             copy_point_list_3d (a, polygon->pts);

             // initial end cap
             new_polygon = raw_polygon (end_opts);
             transform_points (new_polygon->pts, xf, a);
             ADD_TO_OUTPUT (new_polygon);

             for (i = 0; i < s->n_slices; i++)
               {
                 advance_transform_accum (sweep_accum, s->xforms);
                 compose_transform_accum (sweep_xf, sweep_accum, 0);
                 transform_points (b, sweep_xf, polygon->pts);
                 for (jj = a->n_pts - 1, j = 0; j < a->n_pts; jj = j++)
                   make_faces (&r, s->opts, xf, b->v[jj], b->v[j],
                               a->v[j], a->v[jj], split_p);
                 t = a;
                 a = b;
                 b = t;        // swap a and b for next pass
               }

             // final end cap is copy of a (note reverse point order)
             new_polygon = raw_polygon (end_opts);
             reverse_copy_point_list_3d (new_polygon->pts, a);
             transform_points (new_polygon->pts, xf, new_polygon->pts);
             ADD_TO_OUTPUT (new_polygon);
           }
         else
           {
             warn (no_line,
                   "cannot sweep a %s; object ignored (sorry, no line number)",
                   object_type_str[swept->tag]);
           }
       }
   }

 clear_point_list_3d (pts_1);
 clear_point_list_3d (pts_2);
 clear_transform_list (sweep_accum);
 return r;
#undef ADD_TO_OUTPUT
}

// forward declaration
static OBJECT *rev_transformed_flat_scene (OBJECT * obj, TRANSFORM xf);

OBJECT *
flat_repeat (OBJECT * obj, TRANSFORM xf)
{
 int i;
 REPEAT_OBJECT *s = (REPEAT_OBJECT *) obj;
 OBJECT *flat_repeated, *r;
 TRANSFORM_LIST repeat_accum[1];
 TRANSFORM child_xf;

 init_transform_list (repeat_accum);

 if (s->n <= 0)
   return NULL;

 // recursively flatten repeated object in its own coordinates
 flat_repeated = flat_scene (s->repeated, NULL);

 fill_transform_accum (repeat_accum, s->xforms);
 r = NULL;
 for (i = 0; i < s->n; i++)
   {
     compose_transform_accum (child_xf, repeat_accum, xf);
     r = cat_objects (rev_transformed_flat_scene
                      (flat_repeated, child_xf), r);
     advance_transform_accum (repeat_accum, s->xforms);
   }
 // flat_repeated is a memory leak
 return r;
}

OBJECT *
flat_compound (OBJECT * obj, TRANSFORM xf)
{
 COMPOUND_OBJECT *compound = (COMPOUND_OBJECT *) obj;
 TRANSFORM child_xf;
 compose (child_xf, xf, compound->xform);
 return rev_transformed_flat_scene (compound->child, child_xf);
}

typedef OBJECT *(*FLATTEN_FUNC) (OBJECT *, TRANSFORM);

static FLATTEN_FUNC flatten_tbl[] = {
 NULL,                         // O_BASE
 NULL,                         // O_TAG_DEF
 NULL,                         // O_OPTS_DEF
 NULL,                         // O_SCALAR_DEF
 NULL,                         // O_POINT_DEF
 NULL,                         // O_VECTOR_DEF
 NULL,                         // O_TRANSFORM_DEF
 flat_dots,
 flat_line,
 flat_curve,
 flat_polygon,
 flat_special,
 flat_sweep,
 flat_repeat,
 flat_compound,
};

OBJECT *
cat_objects (OBJECT * lft, OBJECT * rgt)
{
 OBJECT *p;

 if (!lft)
   return rgt;
 for (p = lft; p->sibling; p = p->sibling)
   /* skip */ ;
 p->sibling = rgt;
 return lft;
}

static OBJECT *
rev_transformed_flat_scene (OBJECT * obj, TRANSFORM xf)
{
 OBJECT *r = NULL;
 while (obj)
   {
     // flatten the object
     if (flatten_tbl[obj->tag] == NULL)
       die (no_line, "rev_transformed_flat_scene: bad tag %d", obj->tag);

     // join scene sibling lists
     r = cat_objects ((*flatten_tbl[obj->tag]) (obj, xf), r);

     // on to next object
     obj = obj->sibling;
   }
 return r;
}

// call with null env omits camera transformation
OBJECT *
flat_scene (OBJECT * obj, GLOBAL_ENV * env)
{
 FLOAT *camera = env
   && global_env_is_set_p (env, GE_CAMERA) ? env->camera : identity;
 return sibling_reverse (rev_transformed_flat_scene (obj, camera));
}

// ---- overlay/underlay/depth sort flag --------------------------------------

static int
dots_lay_val (OBJECT * obj)
{
 return lay_val (((DOTS_OBJECT *) obj)->opts, LAY_IN);
}

static int
line_lay_val (OBJECT * obj)
{
 return lay_val (((LINE_OBJECT *) obj)->opts, LAY_IN);
}

static int
curve_lay_val (OBJECT * obj)
{
 return lay_val (((CURVE_OBJECT *) obj)->opts, LAY_IN);
}

static int
polygon_lay_val (OBJECT * obj)
{
 return lay_val (((POLYGON_OBJECT *) obj)->opts, LAY_IN);
}

static int
special_lay_val (OBJECT * obj)
{
 return lay_val (((SPECIAL_OBJECT *) obj)->opts, LAY_OVER);
}

typedef int (*LAY_VAL_FUNC) (OBJECT *);

static LAY_VAL_FUNC lay_val_tbl[] = {
 NULL,                         // O_BASE
 NULL,                         // O_TAG_DEF
 NULL,                         // O_OPTS_DEF
 NULL,                         // O_SCALAR_DEF
 NULL,                         // O_POINT_DEF
 NULL,                         // O_VECTOR_DEF
 NULL,                         // O_TRANSFORM_DEF
 dots_lay_val,
 line_lay_val,
 curve_lay_val,
 polygon_lay_val,
 special_lay_val,
 NULL,                         // O_SWEEP (flattened)
 NULL,                         // O_REPEAT (flattened)
 NULL,                         // O_COMPOUND (flattened)
};

int
object_lay_val (OBJECT * obj)
{
 if (!lay_val_tbl[obj->tag])
   die (no_line, "bad tag in object_lay_val");
 return (*lay_val_tbl[obj->tag]) (obj);
}

// ---- binary space partition ------------------------------------------------

static void
add_dots_object_to_bsp_pass_1 (OBJECT * obj, BSP_TREE * bsp)
{
}

static void
add_line_object_to_bsp_pass_1 (OBJECT * obj, BSP_TREE * bsp)
{
}

static void
add_curve_object_to_bsp_pass_1 (OBJECT * obj, BSP_TREE * bsp)
{
}

static void
add_polygon_object_to_bsp_pass_1 (OBJECT * obj, BSP_TREE * bsp)
{
 int i;
 POLYGON_3D *polygon;
 POLYGON_OBJECT *polygon_obj = (POLYGON_OBJECT *) obj;
 PLANE plane[1];

 // copy point list to new polygon
 polygon = new_polygon_3d (polygon_obj->pts->n_pts);
 polygon->n_sides = polygon_obj->pts->n_pts;
 for (i = 0; i < polygon->n_sides; i++)
   copy_pt_3d (polygon->v[i], polygon_obj->pts->v[i]);

 find_polygon_plane (plane, polygon);

 // backface elimination
 // put the new polygon in the tree
 if (plane->n[Z] >= -FLOAT_EPS ||
     !bool_opt_p (polygon_obj->opts, "cull", 1) ||
     !bool_opt_p (global_env->opts, "cull", 1))
   {

     add_polygon_to_bsp (bsp, polygon, obj);
   }
 else
   {
     delete_polygon_3d (polygon);
   }
}

static void
add_special_object_to_bsp_pass_1 (OBJECT * obj, BSP_TREE * bsp)
{
}

typedef void (*BSP_INSERT_FUNC) (OBJECT *, BSP_TREE *);

static BSP_INSERT_FUNC insert_in_bsp_pass_1_tbl[] = {
 NULL,                         // O_BASE
 NULL,                         // O_TAG_DEF
 NULL,                         // O_OPTS_DEF
 NULL,                         // O_SCALAR_DEF
 NULL,                         // O_POINT_DEF
 NULL,                         // O_VECTOR_DEF
 NULL,                         // O_TRANSFORM_DEF
 add_dots_object_to_bsp_pass_1,
 add_line_object_to_bsp_pass_1,
 add_curve_object_to_bsp_pass_1,
 add_polygon_object_to_bsp_pass_1,
 add_special_object_to_bsp_pass_1,
 NULL,                         // O_SWEEP (flattened)
 NULL,                         // O_REPEAT (flattened)
 NULL,                         // O_COMPOUND (flattened)
};

static void
add_dots_object_to_bsp_pass_2 (OBJECT * obj, BSP_TREE * bsp)
{
 int i;
 DOTS_OBJECT *dots_obj = (DOTS_OBJECT *) obj;
 // insert each dot as a polyline with only one vertex
 for (i = 0; i < dots_obj->pts->n_pts; i++)
   {
     POLYLINE_3D *dot = new_polyline_3d (1);
     dot->n_vertices = 1;
     copy_pt_3d (dot->v[0], dots_obj->pts->v[i]);
     add_polyline_to_bsp (bsp, dot, obj);
   }
}

static void
add_line_object_to_bsp_pass_2 (OBJECT * obj, BSP_TREE * bsp)
{
 int i;
 POLYLINE_3D *polyline;
 LINE_OBJECT *line_obj = (LINE_OBJECT *) obj;

 // copy point list to new polyline
 polyline = new_polyline_3d (line_obj->pts->n_pts);
 polyline->n_vertices = line_obj->pts->n_pts;
 for (i = 0; i < line_obj->pts->n_pts; i++)
   copy_pt_3d (polyline->v[i], line_obj->pts->v[i]);

 // fprintf(stderr, "adding to bsp [%p(%d)]\n", line_obj->opts, line_obj->opts->list->n_elts); // DEBUG

 // put the new polyline in the tree
 add_polyline_to_bsp (bsp, polyline, obj);
}

static void
add_curve_object_to_bsp_pass_2 (OBJECT * obj, BSP_TREE * bsp)
{
}

static void
add_polygon_object_to_bsp_pass_2 (OBJECT * obj, BSP_TREE * bsp)
{
}

static void
add_special_object_to_bsp_pass_2 (OBJECT * obj, BSP_TREE * bsp)
{
 SPECIAL_OBJECT *special_obj = (SPECIAL_OBJECT *) obj;
 POLYLINE_3D *special = new_polyline_3d (1);
 special->n_vertices = 1;
 copy_pt_3d (special->v[0], special_obj->pts->v[0]);
 add_polyline_to_bsp (bsp, special, obj);
}

static BSP_INSERT_FUNC insert_in_bsp_pass_2_tbl[] = {
 NULL,                         // O_BASE
 NULL,                         // O_TAG_DEF
 NULL,                         // O_OPTS_DEF
 NULL,                         // O_SCALAR_DEF
 NULL,                         // O_POINT_DEF
 NULL,                         // O_VECTOR_DEF
 NULL,                         // O_TRANSFORM_DEF
 add_dots_object_to_bsp_pass_2,
 add_line_object_to_bsp_pass_2,
 add_curve_object_to_bsp_pass_2,
 add_polygon_object_to_bsp_pass_2,
 add_special_object_to_bsp_pass_2,
 NULL,                         // O_SWEEP (flattened)
 NULL,                         // O_REPEAT (flattened)
 NULL,                         // O_COMPOUND (flattened)
};

// table functions are called in
// OBJECT *hsr_scene_with_bsp(OBJECT *scene);

static void
get_dots_from_polyline (OBJECT * src, OBJECT ** output,
                       BSP_POLYLINE_NODE * polyline_node)
{
 DOTS_OBJECT *dots_src = (DOTS_OBJECT *) src;
 DOTS_OBJECT *new_obj = raw_dots (dots_src->opts);
 copy_point_list_3d (new_obj->pts,
                     (POINT_LIST_3D *) polyline_node->polyline);
 new_obj->sibling = *output;
 *output = (OBJECT *) new_obj;
}

static void
get_line_from_polyline (OBJECT * src, OBJECT ** output,
                       BSP_POLYLINE_NODE * polyline_node)
{
 LINE_OBJECT *line_src = (LINE_OBJECT *) src;
 LINE_OBJECT *new_obj = raw_line (copy_line_opts (line_src->opts,
                                                  polyline_node->first_p,
                                                  polyline_node->last_p,
                                                  global_env->
                                                  output_language));
 copy_point_list_3d (new_obj->pts,
                     (POINT_LIST_3D *) polyline_node->polyline);
 new_obj->sibling = *output;
 *output = (OBJECT *) new_obj;
}

static void
get_curve_from_polyline (OBJECT * src, OBJECT ** output,
                        BSP_POLYLINE_NODE * polyline_node)
{
}

static void
get_polygon_border_from_polyline (OBJECT * src,
                                 OBJECT ** output,
                                 BSP_POLYLINE_NODE * polyline_node)
{
 // no longer used
 POLYGON_OBJECT *polygon_src = (POLYGON_OBJECT *) src;
 LINE_OBJECT *new_obj = raw_line (copy_opts (polygon_src->opts, OPT_LINE,
                                             global_env->output_language));
 copy_point_list_3d (new_obj->pts,
                     (POINT_LIST_3D *) polyline_node->polyline);
 new_obj->sibling = *output;
 *output = (OBJECT *) new_obj;
}

static void
get_special_from_polyline (OBJECT * src, OBJECT ** output,
                          BSP_POLYLINE_NODE * polyline_node)
{
 SPECIAL_OBJECT *special_src = (SPECIAL_OBJECT *) src;
 SPECIAL_OBJECT *new_obj =
   raw_special (copy_opts (special_src->opts, OPT_INTERNAL,
                           global_env->output_language));
 copy_point_list_3d (new_obj->pts, special_src->pts);  // go back to original special since we didn't split
 new_obj->code = safe_strdup (special_src->code);
 new_obj->sibling = *output;
 *output = (OBJECT *) new_obj;
}

typedef void (*GET_OBJ_FROM_POLYLINE_FUNC) (OBJECT * src, OBJECT ** output,
                                           BSP_POLYLINE_NODE *
                                           polyline_node);

static GET_OBJ_FROM_POLYLINE_FUNC get_obj_from_polyline_tbl[] = {
 NULL,                         // O_BASE
 NULL,                         // O_TAG_DEF
 NULL,                         // O_OPTS_DEF
 NULL,                         // O_SCALAR_DEF
 NULL,                         // O_POINT_DEF
 NULL,                         // O_VECTOR_DEF
 NULL,                         // O_TRANSFORM_DEF
 get_dots_from_polyline,
 get_line_from_polyline,
 get_curve_from_polyline,
 get_polygon_border_from_polyline,
 get_special_from_polyline,
 NULL,                         // O_SWEEP (flattened)
 NULL,                         // O_REPEAT (flattened)
 NULL,                         // O_COMPOUND (flattened)
};

void
get_objects_from_bsp_node (BSP_NODE * bsp, void *env)
{
 int i, j, k, broken_border_p;
 OBJECT **output = (OBJECT **) env;

 if (bsp == NULL)
   return;

 if (bsp->tag == BSP_POLYGON)
   {
     OPTS *opts;
     LINE_OBJECT *new_line_obj = NULL;
     BSP_POLYGON_NODE *polygon_node = (BSP_POLYGON_NODE *) bsp;
     POLYGON_OBJECT *src = bsp->attr, *new_polygon_obj;

     broken_border_p = 0;
     for (i = 0; i < polygon_node->polygon->n_sides; i++)
       {
         if (!polygon_node->polygon_attr->elt[i].border_p)
           {
             broken_border_p = 1;
             break;
           }
       }
     if (broken_border_p)
       {

         //  add these options if user didn't specify them
         opts =
           copy_opts (src->opts, OPT_POLYGON, global_env->output_language);
         add_no_edges_default_opt (&opts, global_env->output_language);
         add_solid_white_default_opt (&opts, global_env->output_language);

         new_polygon_obj = raw_polygon (opts);
         copy_point_list_3d (new_polygon_obj->pts,
                             (POINT_LIST_3D *) polygon_node->polygon);
         new_polygon_obj->sibling = *output;
         *output = (OBJECT *) new_polygon_obj;

         // create the border from edges that did not result from splitting
         //
         // find a break in the border if there is one
         for (j = polygon_node->polygon->n_sides - 1, i = 0;
              i < polygon_node->polygon->n_sides; j = i++)
           {
             if (!polygon_node->polygon_attr->elt[j].border_p)
               break;
           }
         if (i == polygon_node->polygon->n_sides)
           i = 0;
         // j->i is now a border edge, which is what we want
         for (k = 0;
              k < polygon_node->polygon->n_sides;
              j = i, i = (i + 1) % polygon_node->polygon->n_sides, k++)
           {
             if (polygon_node->polygon_attr->elt[j].border_p)
               {
                 if (new_line_obj == NULL)
                   {
                     opts =
                       copy_opts (src->opts, OPT_LINE,
                                  global_env->output_language);
                     new_line_obj = raw_line (opts);
                     copy_pt_3d (pushed_point_list_3d_v
                                 (new_line_obj->pts),
                                 polygon_node->polygon->v[j]);
                   }
                 copy_pt_3d (pushed_point_list_3d_v (new_line_obj->pts),
                             polygon_node->polygon->v[i]);
               }
             else if (new_line_obj)
               {
                 new_line_obj->sibling = *output;
                 *output = (OBJECT *) new_line_obj;
                 new_line_obj = NULL;
               }
           }
         if (new_line_obj)
           {
             new_line_obj->sibling = *output;
             *output = (OBJECT *) new_line_obj;
             new_line_obj = NULL;
           }
       }
     else
       {
         opts =
           copy_opts (src->opts, OPT_POLYGON | OPT_LINE,
                      global_env->output_language);
         add_solid_white_default_opt (&opts, global_env->output_language);

         new_polygon_obj = raw_polygon (opts);
         new_polygon_obj->border_p = 1;
         copy_point_list_3d (new_polygon_obj->pts,
                             (POINT_LIST_3D *) polygon_node->polygon);
         new_polygon_obj->sibling = *output;
         *output = (OBJECT *) new_polygon_obj;
       }
   }
 else
   {                           // BSP_POLYLINE
     OBJECT *src = bsp->attr;
     (*get_obj_from_polyline_tbl[src->tag]) (src, output,
                                             (BSP_POLYLINE_NODE *) bsp);
   }
}

OBJECT *
hsr_scene_with_bsp (OBJECT * scene)
{
 OBJECT *p, *t, *underlay, *overlay, *sorted;
 BSP_TREE bsp;

 // two passes are needed to serve the bsp requirement
 // that polylines be inserted after all polygons are
 // already there
 // also take care of underlays and ovelays in the first pass
 bsp = NULL;
 underlay = overlay = sorted = NULL;
 for (p = scene; p; p = p->sibling)
   {
     switch (object_lay_val (p))
       {
       case LAY_UNDER:
         t = copy_drawable (p);
         t->sibling = underlay;
         underlay = t;
         break;
       case LAY_IN:
         (*insert_in_bsp_pass_1_tbl[p->tag]) (p, &bsp);
         break;
       case LAY_OVER:
         t = copy_drawable (p);
         t->sibling = overlay;
         overlay = t;
         break;
       default:
         die (no_line, "bad lay value in hsr_scene_with_bsp");
         break;
       }
   }
 for (p = scene; p; p = p->sibling)
   if (object_lay_val (p) == LAY_IN)
     (*insert_in_bsp_pass_2_tbl[p->tag]) (p, &bsp);
 traverse_bsp (bsp, get_objects_from_bsp_node, &sorted);
 sorted = sibling_reverse (sorted);
 sorted = cat_objects (underlay, sorted);
 sorted = cat_objects (sorted, overlay);
 return sorted;
}

// ---- depth sort ------------------------------------------------------------

static void
add_dots_object_to_sort (OBJECT * obj, BSP_TREE * bsp)
{
 int i;
 DOTS_OBJECT *dots_obj = (DOTS_OBJECT *) obj;
 POLYLINE_3D dot[1];

 init_polyline_3d (dot);
 setup_polyline_3d (dot, 1);
 dot->n_vertices = 1;

 // insert each dot as a polyline with only one vertex
 for (i = 0; i < dots_obj->pts->n_pts; i++)
   {
     copy_pt_3d (dot->v[0], dots_obj->pts->v[i]);
     add_polyline_to_sort (bsp, dot, obj);
   }
 clear_polyline_3d (dot);
}

static void
add_line_object_to_sort (OBJECT * obj, BSP_TREE * bsp)
{
 LINE_OBJECT *line_obj = (LINE_OBJECT *) obj;
 // DANGER: assumes point list in polyline object is congruent to a geometry.h polyline
 add_polyline_to_sort (bsp, (POLYLINE_3D *) line_obj->pts, obj);
}

static void
add_curve_object_to_sort (OBJECT * obj, BSP_TREE * bsp)
{
}

static void
add_polygon_object_to_sort (OBJECT * obj, BSP_TREE * bsp)
{
 POLYGON_OBJECT *polygon_obj = (POLYGON_OBJECT *) obj;
 PLANE plane[1];

 // backface elimination
 // put the new polygon in the tree
 find_polygon_plane (plane, (POLYGON_3D *) polygon_obj->pts);
 if (plane->n[Z] >= -FLOAT_EPS ||
     !bool_opt_p (polygon_obj->opts, "cull", 1) ||
     !bool_opt_p (global_env->opts, "cull", 1))
   add_polygon_to_sort (bsp, (POLYGON_3D *) polygon_obj->pts, obj);
}

static void
add_special_object_to_sort (OBJECT * obj, BSP_TREE * bsp)
{
 SPECIAL_OBJECT *special_obj = (SPECIAL_OBJECT *) obj;
 POLYLINE_3D *special = new_polyline_3d (1);
 special->n_vertices = 1;
 copy_pt_3d (special->v[0], special_obj->pts->v[0]);
 add_polyline_to_sort (bsp, special, obj);
}

typedef void (*ADD_TO_DS_FUNC) (OBJECT *, BSP_TREE *);

static ADD_TO_DS_FUNC add_to_sort_tbl[] = {
 NULL,                         // O_BASE
 NULL,                         // O_TAG_DEF
 NULL,                         // O_OPTS_DEF
 NULL,                         // O_SCALAR_DEF
 NULL,                         // O_POINT_DEF
 NULL,                         // O_VECTOR_DEF
 NULL,                         // O_TRANSFORM_DEF
 add_dots_object_to_sort,
 add_line_object_to_sort,
 add_curve_object_to_sort,
 add_polygon_object_to_sort,
 add_special_object_to_sort,
 NULL,                         // O_SWEEP (flattened)
 NULL,                         // O_REPEAT (flattened)
 NULL,                         // O_COMPOUND (flattened)
};

OBJECT *
hsr_scene_with_depth_sort (OBJECT * scene)
{
 OBJECT *p, *t, *underlay, *overlay, *sorted;
 BSP_TREE bsp;

 // two passes are needed to serve the bsp requirement
 // that polylines be inserted after all polygons are
 // already there
 // also take care of underlays and ovelays in the first pass
 bsp = NULL;
 underlay = overlay = sorted = NULL;
 for (p = scene; p; p = p->sibling)
   {
     switch (object_lay_val (p))
       {
       case LAY_UNDER:
         t = copy_drawable (p);
         t->sibling = underlay;
         underlay = t;
         break;
       case LAY_IN:
         (*add_to_sort_tbl[p->tag]) (p, &bsp);
         break;
       case LAY_OVER:
         t = copy_drawable (p);
         t->sibling = overlay;
         overlay = t;
         break;
       default:
         die (no_line, "bad lay value in hsr_scene_with_bsp");
         break;
       }
   }
 sort_by_depth (&bsp);
 traverse_depth_sort (bsp, get_objects_from_bsp_node, &sorted);
 sorted = sibling_reverse (sorted);
 sorted = cat_objects (underlay, sorted);
 sorted = cat_objects (sorted, overlay);
 return sorted;
}

// ---- extent finding --------------------------------------------------------

static void
get_extent_of_points (POINT_LIST_3D * pts, BOX_3D * e)
{
 int i;

 for (i = 0; i < pts->n_pts; i++)
   fold_min_max_pt_3d (e, pts->v[i]);
}

static void
get_extent_of_dots (OBJECT * obj, BOX_3D * e)
{
 DOTS_OBJECT *dots = (DOTS_OBJECT *) obj;
 get_extent_of_points (dots->pts, e);
}

static void
get_extent_of_line (OBJECT * obj, BOX_3D * e)
{
 LINE_OBJECT *line = (LINE_OBJECT *) obj;
 get_extent_of_points (line->pts, e);
}

static void
get_extent_of_curve (OBJECT * obj, BOX_3D * e)
{
 CURVE_OBJECT *curve = (CURVE_OBJECT *) obj;
 get_extent_of_points (curve->pts, e);
}

static void
get_extent_of_polygon (OBJECT * obj, BOX_3D * e)
{
 POLYGON_OBJECT *polygon = (POLYGON_OBJECT *) obj;
 get_extent_of_points (polygon->pts, e);
}

static void
get_extent_of_special (OBJECT * obj, BOX_3D * e)
{
 SPECIAL_OBJECT *special = (SPECIAL_OBJECT *) obj;
 fold_min_max_pt_3d (e, special->pts->v[0]);
}

typedef void (*EXTENT_FUNC) (OBJECT *, BOX_3D *);

static EXTENT_FUNC extent_tbl[] = {
 NULL,                         // O_BASE
 NULL,                         // O_TAG_DEF
 NULL,                         // O_OPTS_DEF
 NULL,                         // O_SCALAR_DEF
 NULL,                         // O_POINT_DEF
 NULL,                         // O_VECTOR_DEF
 NULL,                         // O_TRANSFORM_DEF
 get_extent_of_dots,
 get_extent_of_line,
 get_extent_of_curve,
 get_extent_of_polygon,
 get_extent_of_special,
 NULL,                         // O_SWEEP (flattened)
 NULL,                         // O_REPEAT (flattened)
 NULL,                         // O_COMPOUND (flattened)
};

void
get_extent (OBJECT * obj, BOX_3D * e, int *n_obj)
{
 if (obj)
   {
     int n = 0;
     init_box_3d(e);
     while (obj)
       {
         if (extent_tbl[obj->tag] == NULL)
           die (no_line, "get_extent: bad tag %d", obj->tag);
         (*extent_tbl[obj->tag]) (obj, e);
         obj = obj->sibling;
         ++n;
       }
     *n_obj = n;
   }
 else
   {
     // reasonable empty box
     e->min[X] = e->min[Y] = e->min[Z] = 0;
     e->max[X] = e->max[Y] = e->max[Z] = 1;
     *n_obj = 0;
   }
}

int
xy_overlap_p (OBJECT * obj, BOX_3D * e)
{
 BOX_3D e_obj[1];

 init_box_3d(e_obj);
 (*extent_tbl[obj->tag]) (obj, e_obj);
 return
   !(e_obj->max[X] < e->min[X] ||
     e_obj->min[X] > e->max[X] ||
     e_obj->max[Y] < e->min[Y] ||
     e_obj->min[Y] > e->max[Y]);
}