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