#include "idbsp.h" /* I assume that a grid 8 is used for the maps, so a point will be considered on a line if it is within 8 pixels of it. The accounts for floating error. */ int cuts; /* number of new lines generated by BSP process */ /* ================== = = DivlineFromWorldline = ================== */ void DivlineFromWorldline (divline_t *d, line_t *w) { d->pt = w->p1; d->dx = w->p2.x - w->p1.x; d->dy = w->p2.y - w->p1.y; } #define FLOAT_ERROR 4.4722 /* ================== = = PointOnSide = = Returns side 0 (front), 1 (back), or -1 (colinear) ================== */ #ifdef _STEVE_FIX_ int PointOnSide (NXPoint *p, divline_t *l) { float dist; if (!l->dx) { if (p->x > l->pt.x-2 && p->x < l->pt.x+2) return -1; if (p->x < l->pt.x) return l->dy > 0; return l->dy < 0; } if (!l->dy) { if (p->y > l->pt.y-2 && p->y < l->pt.y+2) return -1; if (p->y < l->pt.y) return l->dx < 0; return l->dx > 0; } dist = l->dx * (p->y - l->pt.y) - l->dy * (p->x - l->pt.x); if (dist < FLOAT_ERROR && dist > -FLOAT_ERROR) return(-1); else if (dist < -FLOAT_ERROR) return(0); else return(1); } #else int PointOnSide (NXPoint *p, divline_t *l) { float dx,dy; float left, right; float a,b,c,d; if (!l->dx) { if (p->x > l->pt.x-2 && p->x < l->pt.x+2) return -1; if (p->x < l->pt.x) return l->dy > 0; return l->dy < 0; } if (!l->dy) { if (p->y > l->pt.y-2 && p->y < l->pt.y+2) return -1; if (p->y < l->pt.y) return l->dx < 0; return l->dx > 0; } dx = l->pt.x - p->x; dy = l->pt.y - p->y; a = l->dx*l->dx + l->dy*l->dy; b = 2*(l->dx*dx + l->dy*dy); c = dx*dx+dy*dy - 2*2; /* 2 unit radius */ d = b*b - 4*a*c; if (d>0) return -1; /* within four pixels of line */ dx = p->x - l->pt.x; dy = p->y - l->pt.y; left = l->dy * dx; right = dy * l->dx; if ( fabs (left-right) < 0.5 ) /* allow slop */ return -1; /* on line */ if (right < left) return 0; /* front side */ return 1; /* back side */ } #endif /* ============= = = sign = = Returns -1, 0, or 1, based on the input sign = ============== */ int sign (float i) { if (i<0) return -1; else if (i>0) return 1; return 0; } /* ================== = = LineOnSide = = Returns side 0 / 1, or -2 if line must be split = If the line is colinear, it will be placed on the front side if = it is going the same direction as the dividing line ================== */ #ifdef _STEVE_FIX_ int LineOnSide (line_t *wl, divline_t *bl) #else boolean LineOnSide (line_t *wl, divline_t *bl) #endif { int s1,s2; float dx, dy; s1 = PointOnSide (&wl->p1, bl); s2 = PointOnSide (&wl->p2, bl); if (s1 == s2) { if (s1 == -1) { /* colinear, so see if the directions are the same */ dx = wl->p2.x - wl->p1.x; dy = wl->p2.y - wl->p1.y; if (sign(dx) == sign (bl->dx) && sign(dy) == sign(bl->dy) ) return 0; return 1; } return s1; } if (s1 == -1) return s2; if (s2 == -1) return s1; return -2; } /* =============== = = InterceptVector = = Returns the fractional intercept point along first vector =============== */ float InterceptVector (divline_t *v2, divline_t *v1) { #if 0 v1.x + f1*v1.xs = v2.x + f2*v2.xs (parametric x coordinates) f1*v1.xs = v2.x - v1.x + f2*v2.xs f1 = (v2.x - v1.x +f2*v2.xs) / v1.xs v1.y + f1*v1.ys = v2.y + f2*v2.ys (parametric y coordinates) f1 = (v2.y - v1.y + f2*v2.ys) / v1.ys f1 = (v2.x - v1.x +f2*v2.xs) / v1.xs = (v2.y - v1.y + f2*v2.ys) / v1.ys v1.ys*v2.x - v1.ys*v1.x + v1.ys*v2.xs*f2 = v1.xs*v2.y - v1.xs*v1.y + v1.xs*v2.ys*f2 (v1.ys*v2.xs - v1.xs*v2.ys)*f2 = -v1.ys*v2.x + v1.ys*v1.x + v1.xs*v2.y - v1.xs*v1.y = v1.ys*(v1.x-v2.x) + v1.xs*(v2.y-v1.y) f2 = (v1.ys*(v1.x-v2.x) + v1.xs*(v2.y-v1.y)) / (v1.ys*v2.xs - v1.xs*v2.ys) #endif float frac, num, den; den = v1->dy*v2->dx - v1->dx*v2->dy; if (den == 0) Error ("InterceptVector: parallel"); num = (v1->pt.x - v2->pt.x)*v1->dy + (v2->pt.y - v1->pt.y)*v1->dx; frac = num / den; if (frac <= 0.0 || frac >= 1.0) Error ("InterceptVector: intersection outside line"); return frac; } /* ================== = = CutLine = = Truncates the given worldline to the front side of the divline = and returns the cut off back side in a newly allocated worldline ================== */ float round (float x) { if (x>0) { if (x - (int)x < 0.1) return (int)x; else if (x - (int)x > 0.9) return (int)x+1; else return x; } if ((int)x - x < 0.1) return (int)x; else if ((int)x - x > 0.9) return (int)x - 1; return x; } line_t *CutLine (line_t *wl, divline_t *bl) { int side; line_t *new_p; divline_t wld; float frac; NXPoint intr; int offset; cuts++; DivlineFromWorldline (&wld, wl); new_p = (line_t *)malloc (sizeof(line_t)); memset (new_p,0,sizeof(*new_p)); *new_p = *wl; frac = InterceptVector (&wld, bl); #ifdef _STEVE_FIX_ intr.x = wld.pt.x + (wld.dx*frac); intr.y = wld.pt.y + (wld.dy*frac); offset = wl->offset + (frac*sqrt(wld.dx*wld.dx+wld.dy*wld.dy)); #else intr.x = wld.pt.x + round(wld.dx*frac); intr.y = wld.pt.y + round(wld.dy*frac); offset = wl->offset + round(frac*sqrt(wld.dx*wld.dx+wld.dy*wld.dy)); #endif side = PointOnSide (&wl->p1, bl); if (side == 0) { /* line starts on front side */ wl->p2 = intr; new_p->p1 = intr; new_p->offset = offset; } else { /* line starts on back side */ wl->p1 = intr; wl->offset = offset; new_p->p2 = intr; } return new_p; } /* ================ = = EvaluateSplit = = Returns a number grading the quality of a split along the givent line = for the current list of lines. Evaluation is halted as soon as it is = determined that a better split already exists = = A split is good if it divides the lines evenly without cutting many lines = A horizontal or vertical split is better than a sloping split = = The LOWER the returned value, the better. If the split line does not divide = any of the lines at all, MAXINT will be returned ================ */ /*int EvaluateSplit (id lines_i, line_t *spliton, int bestgrade) */ int EvaluateSplit(STORAGE *lines_i, line_t *spliton, int bestgrade) { int i,c,side; line_t *line_p; divline_t divline; int frontcount, backcount, max, new; int grade; worldline_t *wl; /* wl = [linestore_i elementAt: spliton->linedef]; */ wl = (worldline_t *)linestore_i->data + spliton->linedef; #if 0 if (wl->special == BSPSLIDEENDSPECIAL) return MAXINT; /* NEVER split on this, because it moves */ #endif DivlineFromWorldline (&divline, spliton); frontcount = backcount = 0; /* c = [lines_i count]; */ c = lines_i->count; grade = 0; for (i=0 ; idata + i; if (line_p == spliton) side = 0; else side = LineOnSide (line_p, &divline); switch (side) { case 0: frontcount++; break; case 1: backcount++; break; case -2: /* wl = [linestore_i elementAt: line_p->linedef]; */ wl = (worldline_t *)linestore_i->data + line_p->linedef; #if 0 if (wl->special == BSPSLIDESIDESPECIAL) return MAXINT; /* NEVER split this line, because it slides */ #endif frontcount++; backcount++; break; } max = MAX(frontcount,backcount); new = (frontcount+backcount) - c; grade = max+new*8; if (grade > bestgrade) return grade; /* might as well stop now */ } if (frontcount == 0 || backcount == 0) return INT_MAX; /* line does not partition at all */ return grade; } /* ================ = = ExecuteSplit = = Actually splits the line list as EvaluateLines predicted ================ */ /* void ExecuteSplit (id lines_i, line_t *spliton , id frontlist_i, id backlist_i) */ void ExecuteSplit(STORAGE *lines_i, line_t *spliton, STORAGE *frontlist_i, STORAGE *backlist_i) { int i,c,side; line_t *line_p, *newline_p; divline_t divline; DivlineFromWorldline (&divline, spliton); DrawDivLine (&divline); /* c = [lines_i count]; */ c = lines_i->count; for (i=0 ; idata + i; if (line_p == spliton) side = 0; else side = LineOnSide (line_p, &divline); switch (side) { case 0: /* [frontlist_i addElement: line_p]; */ memcpy((line_t *)frontlist_i->data + frontlist_i->count, line_p, sizeof(line_t)); frontlist_i->count += 1; frontlist_i->data = (line_t *)realloc(frontlist_i->data, sizeof(line_t) * (frontlist_i->count + 1)); break; case 1: /* [backlist_i addElement: line_p]; */ memcpy((line_t *)backlist_i->data + backlist_i->count, line_p, sizeof(line_t)); backlist_i->count += 1; backlist_i->data = (line_t *)realloc(backlist_i->data, sizeof(line_t) * (backlist_i->count + 1)); break; case -2: newline_p = CutLine (line_p, &divline); /* [frontlist_i addElement: line_p]; [backlist_i addElement: newline_p]; */ memcpy((line_t *)frontlist_i->data + frontlist_i->count, line_p, sizeof(line_t)); frontlist_i->count += 1; frontlist_i->data = (line_t *)realloc(frontlist_i->data, sizeof(line_t) * (frontlist_i->count + 1)); memcpy((line_t *)backlist_i->data + backlist_i->count, newline_p, sizeof(line_t)); backlist_i->count += 1; backlist_i->data = (line_t *)realloc(backlist_i->data, sizeof(line_t) * (backlist_i->count + 1)); break; default: Error ("ExecuteSplit: bad side"); } } } /* ================ = = BSPList = = Takes a storage of lines and recursively partitions the list = Returns a bspnode_t ================ */ /* float gray = NX_WHITE; */ float gray = 0; /* bspnode_t *BSPList (id lines_i) */ bspnode_t *BSPList(STORAGE *lines_i) { /* id frontlist_i, backlist_i; */ STORAGE *frontlist_i, *backlist_i; int i,c, step; line_t *line_p, *bestline_p; int v, bestv; bspnode_t *node_p; /* if (draw) PSsetgray (gray); gray = 1.0 - gray; */ DrawLineStore (lines_i); node_p = (bspnode_t *)malloc (sizeof(*node_p)); memset (node_p, 0, sizeof(*node_p)); /* find the best line to partition on */ /* c = [lines_i count]; */ c = lines_i->count; bestv = INT_MAX; bestline_p = NULL; step = (c/40)+1; /* set this to 1 for an exhaustive search */ research: for (i=0 ; idata + i; v = EvaluateSplit (lines_i, line_p, bestv); if (v 1) { /* possible to get here with non convex area if BSPSLIDE specials caused rejections */ step = 1; goto research; } node_p->lines_i = lines_i; return node_p; } /* divide the line list into two nodes along the best split line */ DivlineFromWorldline (&node_p->divline, bestline_p); /* frontlist_i = [[Storage alloc] initCount: 0 elementSize: sizeof(line_t) description: NULL]; backlist_i = [[Storage alloc] initCount: 0 elementSize: sizeof(line_t) description: NULL]; */ frontlist_i = (STORAGE *)SafeMalloc(sizeof(STORAGE)); frontlist_i->count = 0; frontlist_i->size = sizeof(line_t); frontlist_i->data = (line_t *)SafeMalloc(sizeof(line_t)); backlist_i = (STORAGE *)SafeMalloc(sizeof(STORAGE)); backlist_i->count = 0; backlist_i->size = sizeof(line_t); backlist_i->data = (line_t *)SafeMalloc(sizeof(line_t)); ExecuteSplit (lines_i, bestline_p, frontlist_i, backlist_i); /* recursively divide the lists */ node_p->side[0] = BSPList (frontlist_i); node_p->side[1] = BSPList (backlist_i); return node_p; } /* ===================== = = MakeSegs = ===================== */ /* id segstore_i; */ STORAGE *segstore_i; void MakeSegs (void) { int i, count; worldline_t *wl; /* line_t li; */ line_t *li; /* segstore_i = [[Storage alloc] initCount: 0 elementSize: sizeof(line_t) description: NULL]; */ segstore_i = (STORAGE *)SafeMalloc(sizeof(STORAGE)); segstore_i->data = (line_t *)SafeMalloc(sizeof(line_t)); segstore_i->count = 0; segstore_i->size = sizeof(line_t); /* count = [linestore_i count]; wl = [linestore_i elementAt:0]; */ count = linestore_i->count; wl = linestore_i->data; li = segstore_i->data; for (i= 0 ; ip1 = wl->p1; li->p2 = wl->p2; li->linedef = i; li->side = 0; li->offset = 0; li->grouped = false; /* [segstore_i addElement: &li]; */ segstore_i->count += 1; segstore_i->data = (line_t *)realloc(segstore_i->data, sizeof(line_t) * (segstore_i->count + 1)); li = (line_t *)segstore_i->data + segstore_i->count; if (wl->flags & ML_TWOSIDED) { li->p1 = wl->p2; li->p2 = wl->p1; li->linedef = i; li->side = 1; li->offset = 0; li->grouped = false; /* [segstore_i addElement: &li]; */ segstore_i->count += 1; segstore_i->data = (line_t *)realloc(segstore_i->data, sizeof(line_t) * (segstore_i->count + 1)); li = (line_t *)segstore_i->data + segstore_i->count; } } } /* ===================== = = BuildBSP = ===================== */ bspnode_t *startnode; void BuildBSP (void) { MakeSegs (); cuts = 0; startnode = BSPList (segstore_i); }