shithub: dmap

ref: 901503e77b462db789885a3b9424ad00434b0699
dir: /bsp.c/

View raw version
#include <u.h>
#include <libc.h>
#include <draw.h>
#include "dat.h"
#include "fns.h"

/* a grid 8 is used for maps; points are considered on a line if they are
 * within 8px of it; this accounts for floating error */

typedef struct Bsp Bsp;
typedef struct Divline Divline;
typedef struct Node Node;

enum{
	Maxv = 0x7fffffff,
};

struct Divline{
	Point p;
	float dx;
	float dy;
};
struct Bsp{
	Seg *s;
	Divline;
	Bsp *side[2];
};
Seg *segs;

struct Node{
	short x;
	short y;
	short dx;
	short dy;
	short bound[2][4];
	ushort child[2];
};
Node *nodes;
int nnodes;

static float bbox[4];

void *
erealloc(void *p, ulong n)
{
	if((p = realloc(p, n)) == nil)
		sysfatal("realloc: %r");
	return p;
}

int
subsect(Seg *s)
{
	USED(s);
	return 0;
}

int
mknodes(Bsp *b, short box[4])
{
	int i, f;
	short sbox[2][4];

	if(b->s != nil){
		f = subsect(b->s);
		for(i=0; i<4; i++)
			box[i] = bbox[i];
		return f | 1<<15;
	}
	/* ... */
	return 0;
}

static int
fsign(float f)
{
	return f < 0. ? -1 : f > 0.;
}

static float
fround(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;
}

static void
divline(Divline *d, Seg *s)
{
	d->p = s->p1;
	d->dx = s->p2.x - s->p1.x;
	d->dy = s->p2.y - s->p1.y;
}

/* fractional intercept point along first vector */
static float
InterceptVector(Divline *v2, Divline *v1)
{
	float frac, num, den;

	den = v1->dy * v2->dx - v1->dx * v2->dy;
	if(den == 0.)
		sysfatal("InterceptVector: parallel");
	num = (v1->p.x - v2->p.x) * v1->dy + (v2->p.y - v1->p.y) * v1->dx;
	frac = num / den;
	if(frac <= 0. || frac >= 1.)
		sysfatal("InterceptVector: intersection outside line");
	return frac;
}

static int
ptside(Point *p, Divline *l)
{
	float dx, dy, left, right, a, b, c, d;

	if(l->dx == 0){
		if (p->x > l->p.x - 2 && p->x < l->p.x + 2)
			return -1;
		if (p->x < l->p.x)
			return l->dy > 0;
		return l->dy < 0;
	}
	if(l->dy == 0){
		if(p->y > l->p.y - 2 && p->y < l->p.y + 2)
			return -1;
		if(p->y < l->p.y)
			return l->dx < 0;
		return l->dx > 0;
	}
	dx = l->p.x - p->x;
	dy = l->p.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; /* colinear: within 4px of line */
	dx = p->x - l->p.x;
	dy = p->y - l->p.y;
	left = l->dy * dx;
	right = dy * l->dx;
	if(abs(left - right) < 0.5)	/* allow slope on line */
		return -1;
	if(right < left)
		return 0;	/* front */
	return 1;	/* back */
}

/* if the line is colinear, it will be placed on the front side if going
 * the same direction as the dividing line */
static int
lineside(Seg *wl, Divline *bl)
{
	int s1, s2;
	float dx, dy;

	s1 = ptside(&wl->p1, bl);
	s2 = ptside(&wl->p2, bl);
	if(s1 == s2){	/* colinear: see if the directions are the same */
		if(s1 == -1){
			dx = wl->p2.x - wl->p1.x;
			dy = wl->p2.y - wl->p1.y;
			if(fsign(dx) == fsign(bl->dx)
			&& fsign(dy) == fsign(bl->dy))
				return 0;
			return 1;
		}
		return s1;
	}
	if(s1 == -1)
		return s2;
	if(s2 == -1)
		return s1;
	return -2;	/* line must be split */
}

/* truncate given world line to the front side of the divline and return
 * the cut off back side in a newly allocated world line */
static Seg *
cutline(Seg *s, Divline *bl)
{
	int side, ofs;
	float f;
	Point intr;
	Seg *p;
	Divline d;

	divline(&d, s);
	p = emalloc(sizeof *p);
	*p = *s;
	f = InterceptVector(&d, bl);
	intr.x = d.p.x + fround(d.dx * f);
	intr.y = d.p.y + fround(d.dy * f);
	ofs = s->ofs + fround(f * sqrt(d.dx * d.dx + d.dy * d.dy));
	side = ptside(&s->p1, bl);
	if(side == 0){	/* line starts on front side */
		s->p2 = intr;
		p->p1 = intr;
		p->ofs = ofs;
	}else{	/* line starts on back side */
		s->p1 = intr;
		s->ofs = ofs;
		p->p2 = intr;
	}
	return p;
}

static void
addnode(Seg **ss, int *n, Seg *s, Seg *p)
{
	if(s - *ss >= *n){
		*n += 64;
		*ss = erealloc(*ss, *n * sizeof **ss);
	}
	*s = *p;
}

/* actually split line list as EvaluateLines predicted */
static void
split(Seg *ss, Seg *s, Seg **front, Seg **back)
{
	int side, be, fe;
	Seg *p, *q, *b, *f;
	Divline d;

	fe = be = ss->e - s;
	f = *front = emalloc(fe * sizeof **front);
	b = *back = emalloc(be * sizeof **back);
	for(p=ss; p<ss->e; p++){
		side = p == s ? 0 : lineside(p, &d);
		switch(side){
		case 0: addnode(front, &fe, f++, p); break;
		case 1: addnode(back, &be, b++, p); break;
		case -2:
			q = cutline(p, &d);
			addnode(front, &fe, f++, p);
			addnode(back, &be, b++, q);
			break;
		}
	}
	(*front)->e = f;
	(*back)->e = b;
}

/* grade quality of a split along given 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 sloped
 * split. the lower the returned value, the better. if the split line does
 * not divide any of the lines at all, Maxv is returned */
static int
evalsplit(Seg *ss, Seg *s, int bestv)
{
	int side, s1, s2, v;
	Divline d;
	Seg *p;

	divline(&d, s);
	for(p=ss, v=s1=s2=0; p<ss->e; p++){
		side = p == s ? 0 : lineside(p, &d);
		switch(side){
		case -2: s2++; /* wet floor */
		case 0: s1++; break;
		case 1: s2++; break;
		}
		v = (s1 > s2 ? s1 : s2) + (s1 + s2 - (p - ss)) * 8;
		if(v > bestv)
			return v;
	}
	if(s1 == 0 || s2 == 0)
		return Maxv;
	return v;
}

/* recursively partition seg list */
static Bsp *
bsp(Seg *ss)
{
	int v, bestv;
	Bsp *b;
	Seg *s, *bests, *front, *back;

	b = emalloc(sizeof *b);
	/* find the best line to partition on */
	for(s=ss, bestv=Maxv, bests=nil; s<ss->e; s++){
		v = evalsplit(ss, s, bestv);
		if(v < bestv){
			bestv = v;
			bests = s;
		}
	}
	/* if none of the lines should be split, the remaining lines
	 * are convex, and form a terminal node */
	if(bestv == Maxv){
		b->s = ss;
		return b;
	}
	/* divide the line list into two nodes along the best split line */
	divline(b, bests);
	split(ss, bests, &front, &back);
	/* recursively divide the lists */
	b->side[0] = bsp(front);
	b->side[1] = bsp(back);
	return b;
}

static void
mkseg(void)
{
	Line *l;
	Seg *s;

	/* FIXME: free all nodes and segs first */
	segs = emalloc(nsides * sizeof *segs);
	for(s=segs, l=lines; l<lines+nlines; s++, l++){
		s->p1 = *l->v;
		s->p2 = *l->w;
		s->l = l;
		s->s = l->r;
		if(l->f & LFtwosd){
			s++;
			s->p1 = *l->v;
			s->p2 = *l->w;
			s->l = l;
			s->s = l->l;
		}
	}
	segs->e = s;
}

void
buildnodes(void)
{
	Bsp *b;
	short bounds[4];

	mkseg();
	b = bsp(segs);
	nnodes = nsides;
	nodes = emalloc(nnodes * sizeof *nodes);
	mknodes(b, bounds);
	/* FIXME: free everything */
}