/* 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 #include #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]); }