/* TreeView.m - Copyright 1992  Steve Ludtke  All Rights Reserved      */
/* This object draws lines in 3d with perspective. It has 2 modes if   */
/* dtype==0, a grid is drawn around the current position at z=0 and    */
/* a line is drawn from the "ground" to the Root Branch. Movement      */
/* between trees occurs above the ground. If dtype==1, there is no     */
/* "ground" or grid and motion occurs on a straight line.	       */

#import "TreeView.h"
#import <appkit/View.h>
#import <appkit/Window.h>
#import <appkit/Control.h>
#import <appkit/Application.h>
#import <strings.h>
#import <dpsclient/psops.h>
#import <libc.h>
#import <math.h>

#define SQR(x) ((x)*(x))
@implementation TreeView

/* initialize */
- initFrame:(NXRect *)myrect
{
    [super initFrame:myrect];
    bgColor = NX_WHITE;
    fgColor = NX_BLACK;

    xsca = NX_WIDTH(&bounds) / 2.0;	/* scaling for display in view */
    zsca = NX_HEIGHT(&bounds) / 2.0;
    xof = NX_MIDX(&bounds);
    zof = NX_MIDY(&bounds);

    dtype = 0;

    myx = myy = myz = 0.0;
    [self setView:0.0 alt:M_PI / 2.0 aov:0.8];
    return self;
}

/* get useful pointers */
- start:(Root *) Ptop :Pobject :(char *)path
{
    top = Ptop;
    object = Pobject;
    return self;
}

/* one time step, use for keyboard motion */
- step:(Branch *) myloc
{
    static float        tx, ty, t;

    myx += dx;			/* move viewpoint by dx,dy,dz */
    myy += dy;
    myz += dz;

 /* rotate about fixed target by dc radians */
    if (dc != 0.0) {
	tx = myx - myloc->x;
	ty = myy - myloc->y;
	myx = tx * cos(dc) - ty * sin(dc) + myloc->x;
	myy = tx * sin(dc) + ty * cos(dc) + myloc->y;
    }
 /* move towards/away from target */
    if (dr != 0.0) {
	tx = myx - myloc->x;
	ty = myy - myloc->y;
	t = sqrt(SQR(tx) + SQR(ty));
	myx += tx * dr / t;
	myy += ty * dr / t;
    }
 /* refresh if we moved */
    if (dx != 0.0 || dy != 0.0 || dz != 0.0 || dr != 0.0 || dc != 0.0) {
	[self refresh:myloc :80];
    }
    return self;
}

/* These methods recieve keystrokes */
- (BOOL)acceptsFirstResponder
{
    return YES;
}

/* parse keyboard input */
- keyDown:(NXEvent *)event
{
    char                c;

    c = event->data.key.charCode;
    switch (c) {
    case 'i':
	dz +=.1;
	break;
    case 'm':
	dz -=.1;
	break;
    case 'j':
	dc = -.1;
	break;
    case 'k':
	dc =.1;
	break;
    case 'z':
	dr =.1;
	break;
    case 'a':
	dr = -.1;
	break;
    case '1':
	dtype = 0;		/* enter planar mode */
	break;
    case '3':
	dtype = 1;		/* enter full 3d mode */
	break;
    }
    return (self);
}

- keyUp:(NXEvent *)event
{
    char                c;

    c = event->data.key.charCode;
    switch (c) {
    case 'm':
    case 'i':
	dz = 0.0;
	break;
    case 'j':
    case 'k':
	dc = 0.0;
	break;
    case 'a':
    case 'z':
	dr = 0.0;
	break;
    }
    return (self);
}


/* look at a particular point */
- lookAt:(float)x y:(float)y z:(float)z
{
    static float        r;

    r = sqrt(SQR(x - myx) + SQR(y - myy));
    chi = -atan2(x - myx, y - myy);
    theta = atan2(z - myz, r);
    [self setView:chi alt:theta aov:0.8];
/*#ifdef DEBUG
printf("lookAt:%f,%f   %f,%f,%f\n",theta*57.0,chi*57.0,x-myx,y-my,z-mz);
#endif */

    return (self);
}


/* Build a 3-d transformation matrix without		 */
/* perspective (added when drawn). aov == angle of view	 */
/* az == azimuthal angle   alt == altitude (angle)       */
- setView:(float)az alt:(float)alt aov:(float)aov
{

    xfm[0] = cos(az);
    xfm[1] = sin(az);
    xfm[2] = 0.0;
    xfm[3] = -cos(alt) * sin(az);
    xfm[4] = cos(alt) * cos(az);
    xfm[5] = sin(alt);
    xfm[6] = sin(alt) * sin(az);
    xfm[7] = -sin(alt) * cos(az);
    xfm[8] = cos(alt);
    yas = tan(aov);

    return self;
}

/* The "meat" of the TreeView */
- drawSelf:(NXRect *)rects :(int)rectCount
{
    float               x, y, z, tmx, tmy, tmz, xs, ys, zs, xe, ye, ze, x1, y1, z1;
    Root               *cur;

/*#ifdef DEBUG
printf("Display alt:%f az:%f\n",Dalt*57.296,Daz*57.296);
#endif */

/* bounding box for DPSDoUserPath */
    bbox[0] = bbox[1] = 50.0;
    bbox[2] = bbox[3] = 51.0;


/* clear the view */
    PSsetlinewidth(0.0);
    PSsetgray(NX_BLACK);
    NXRectFill(&bounds);
    PSsetgray(fgColor = NX_WHITE);

    cur = top;
    pathc = 0;

/* if we're in 3d mode, draw the grid and the sun */
 /* draw the sun */
    tmx = 1500000.0;
    tmy = 32000.0;
    tmz = 200000.0;
    y = xfm[3] * tmx + xfm[4] * tmy + xfm[5] * tmz;
    x = (xfm[0] * tmx + xfm[1] * tmy);
    z = (xfm[6] * tmx + xfm[7] * tmy + xfm[8] * tmz);
    if (y > 0.0) {
	x = x / (y * yas) * zsca + xof;
	z = z / (y * yas) * zsca + zof;
	PSsetgray(fgColor =.6667);
	PSarc(x, z, 20.0, 0, 360.0);
	PSfill();
	PSstroke();
    }
 /* draw the grid */
    PSsetgray(fgColor =.5);
 /* draw 2d grid */
    if (dtype == 0) {
    /* this insures that the grid moves correctly */
	xs = floor(myx / 30.0) * 30.0 - 400.0 - myx;
	ys = floor(myy / 30.0) * 30.0 - 400.0 - myy;
	xe = xs + 800.0;
	ye = ys + 800.0;
	tmz = -myz;

	for (tmx = xs; tmx <= xe; tmx += 30.0) {
	    tmy = ys;
	    y = xfm[3] * tmx + xfm[4] * tmy + xfm[5] * tmz;
	    x = (xfm[0] * tmx + xfm[1] * tmy);
	    z = (xfm[6] * tmx + xfm[7] * tmy + xfm[8] * tmz);

	    tmy = ye;
	    y1 = xfm[3] * tmx + xfm[4] * tmy + xfm[5] * tmz;
	    x1 = (xfm[0] * tmx + xfm[1] * tmy);
	    z1 = (xfm[6] * tmx + xfm[7] * tmy + xfm[8] * tmz);
	    [self addline:x :y :z :x1 :y1 :z1];
	}

	for (tmy = ys; tmy <= ye; tmy += 30.0) {
	    tmx = xs;
	    y = xfm[3] * tmx + xfm[4] * tmy + xfm[5] * tmz;
	    x = (xfm[0] * tmx + xfm[1] * tmy);
	    z = (xfm[6] * tmx + xfm[7] * tmy + xfm[8] * tmz);

	    tmx = xe;
	    y1 = xfm[3] * tmx + xfm[4] * tmy + xfm[5] * tmz;
	    x1 = (xfm[0] * tmx + xfm[1] * tmy);
	    z1 = (xfm[6] * tmx + xfm[7] * tmy + xfm[8] * tmz);
	    [self addline:x :y :z :x1 :y1 :z1];
	}
    } else {
    /* 3d grid - Too slow, need to convert to use DPSuserpath... */
/*
	    xs = floor(myx / 100.0) * 100.0 - 400.0 - myx;
	    ys = floor(myy / 100.0) * 100.0 - 400.0 - myy;
	    zs = floor(myz / 100.0) * 100.0 - 400.0 - myz;
	    xe = xs + 800.0;
	    ye = ys + 800.0;
	    ze = zs + 800.0;
    
	    for (tmx = xs; tmx <= xe; tmx += 100.0) {
		for (tmy=ys; tmy<=ye; tmy +=100.0) {
		    for (tmz=zs; tmz<=ze; tmz+=100.0) {
	                y = xfm[3] * tmx + xfm[4] * tmy + xfm[5] * tmz;
	                x = (xfm[0] * tmx + xfm[1] * tmy);
	                z = (xfm[6] * tmx + xfm[7] * tmy + xfm[8] * tmz);
            
	                [self add_dot:x :y :z];
		    }
		}
	    }
	*/
    }
    if (pathc != 0)
	DPSDoUserPath(path, pathc * 2, dps_float, com, pathc, bbox, dps_ustroke);
    pathc = 0;
    PSsetgray(fgColor = NX_WHITE);

    while (cur != NULL) {
	if (cur->drawme) {
	/* normal 3d display */
	    tmx = cur->branch.x - myx;
	    tmy = cur->branch.y - myy;
	    if (dtype == 0)
		tmz = -myz;
	    else
		tmz = cur->branch.z - myz;

	/* do the x-form, perspective is added later */
	    y = xfm[3] * tmx + xfm[4] * tmy + xfm[5] * tmz;
	    x = (xfm[0] * tmx + xfm[1] * tmy);
	    z = (xfm[6] * tmx + xfm[7] * tmy + xfm[8] * tmz);
	/* recursive tree branching */
	    [self Draw:&cur->branch:x :y :z :0];
/*#ifdef DEBUG
printf("Display:%s %f,%f,%f\n",cur->name,x,y,z);
#endif */
	}
    /* repeat for all Roots */
	cur = cur->next;
    }

    if (pathc != 0)
	DPSDoUserPath(path, pathc * 2, dps_float, com, pathc, bbox, dps_ustroke);
    return self;
}

/* this is the recursive routine that draws the branches */
- Draw:(Branch *) topb :(float)x :(float)y :(float)z :(int)f
{
    float               cx, cy, cz;
    float               tmx, tmy, tmz;
    Branch             *cur;

/*#ifdef DEBUG
printf("Draw: %s\n",topb->dname);
#endif*/

    cur = topb;
    while (cur != NULL) {
	tmx = cur->x - myx;
	tmy = cur->y - myy;
	tmz = cur->z - myz;
	cy = xfm[3] * tmx + xfm[4] * tmy + xfm[5] * tmz;
	cx = (xfm[0] * tmx + xfm[1] * tmy);
	cz = (xfm[6] * tmx + xfm[7] * tmy + xfm[8] * tmz);
	[self addline:x :y :z :cx :cy :cz];
	if (f)
	    return (self);
	if (cur->sub != NULL)
	/* this is a "tree" link, so keep going recursively */
	    [self Draw:cur->sub:cx :cy :cz :0];
	else if (cur->link != NULL) {
	/* this is a "web" link, so we need to stop the recursion */
	    [self Draw:cur->link:cx :cy :cz :1];
	}
	cur = cur->next;
    }

    return (self);
}

/* 2d routine to add lines to the view */
- addline:(float)x1 :(float)y1 :(float)x2 :(float)y2
{
    static float        lastx, lasty;

    if (x1 != lastx || y1 != lasty) {
	com[pathc] = dps_moveto;
	path[pathc * 2] = x1;
	path[pathc * 2 + 1] = y1;
	pathc++;
	if (x1 > bbox[2])
	    bbox[2] = x1;
	if (y1 > bbox[3])
	    bbox[3] = y1;
	if (x1 < bbox[0])
	    bbox[0] = x1;
	if (y1 < bbox[1])
	    bbox[1] = y1;
    }
    com[pathc] = dps_lineto;
    path[pathc * 2] = x2;
    path[pathc * 2 + 1] = y2;
    pathc++;
    if (x2 > bbox[2])
	bbox[2] = x2;
    if (y2 > bbox[3])
	bbox[3] = y2;
    if (x2 < bbox[0])
	bbox[0] = x2;
    if (y2 < bbox[1])
	bbox[1] = y2;

    if (pathc >= 1500) {
	DPSDoUserPath(path, pathc * 2, dps_float, com, pathc, bbox, dps_ustroke);
	bbox[0] = bbox[1] = 50.0;
	bbox[2] = bbox[3] = 51.0;
	pathc = 0;
    }
    lastx = x2;
    lasty = y2;
    return (self);
}

/* draw a small circle (slow) */
- add_dot:(float)x :(float)y :(float)z
{
    x = x / (y * yas) * zsca + xof;
    z = z / (y * yas) * zsca + zof;

    PSarc(x, z, 2.0, 0.0, 360.0);
    PSstroke();
    return self;
}

/* 3d routine to add lines to the view. Handles 3d clipping and perspecive */
- addline:(float)x1 :(float)y1 :(float)z1 :(float)x2 :(float)y2 :(float)z2
{
    static float        lastx, lasty;
    float               t;

/* 3d clipping (for points "behind" the screen) */
    if (y1 < 0.0 && y2 < 0.0)
	return self;
    if (y2 < 0.0) {
	t = x2;
	x2 = x1;
	x1 = t;
	t = y2;
	y2 = y1;
	y1 = t;
	t = z2;
	z2 = z1;
	z1 = t;
    }
    if (y1 < 0.0) {
	x1 = x2 - (y2 -.5) * (x2 - x1) / (y2 - y1);
	z1 = z2 - (y2 -.5) * (z2 - z1) / (y2 - y1);
	y1 = 0.5;
    }
/* perspective */
    x1 = x1 / (y1 * yas) * zsca + xof;
    x2 = x2 / (y2 * yas) * zsca + xof;
    z1 = z1 / (y1 * yas) * zsca + zof;
    z2 = z2 / (y2 * yas) * zsca + zof;

/* add the line to the list to draw (x1,z1) - (x2,z2) */
    if (x1 != lastx || z1 != lasty) {
	com[pathc] = dps_moveto;
	path[pathc * 2] = x1;
	path[pathc * 2 + 1] = z1;
	pathc++;
	if (x1 > bbox[2])
	    bbox[2] = x1;
	if (z1 > bbox[3])
	    bbox[3] = z1;
	if (x1 < bbox[0])
	    bbox[0] = x1;
	if (z1 < bbox[1])
	    bbox[1] = z1;
    }
    com[pathc] = dps_lineto;
    path[pathc * 2] = x2;
    path[pathc * 2 + 1] = z2;
    pathc++;
    if (x2 > bbox[2])
	bbox[2] = x2;
    if (z2 > bbox[3])
	bbox[3] = z2;
    if (x2 < bbox[0])
	bbox[0] = x2;
    if (z2 < bbox[1])
	bbox[1] = z2;

/* if we have too many lines, draw them and empty the buffer */
    if (pathc >= 1500) {
	DPSDoUserPath(path, pathc * 2, dps_float, com, pathc, bbox, dps_ustroke);
	bbox[0] = bbox[1] = 50.0;
	bbox[2] = bbox[3] = 51.0;
	pathc = 0;
    }
    lastx = x2;
    lasty = z2;
    return (self);
}

/* handles movement between branches */
- refresh:(Branch *) myloc :(int)speed;
{
    static Branch      *last = NULL;
    float               xs, ys, zs;
    float               xls, yls, zls;
    float               f, sp1, sp2;

/*#ifdef DEBUG
printf("refresh: %f %f %f\n",myloc->x,myloc->y,myloc->z);
#endif */
    if (myloc == NULL) {
	last = NULL;
	myloc = &top->branch;
	speed = 0;
    }
    if (last == NULL) {
	last = myloc;
	myx = myloc->x - 2.0;
	myy = myloc->y - 2.0;
	myz = myloc->z;
    }
/* turn the speed into something usable */
    sp1 = 30.0;
    sp2 = 51.2;
    if (speed <= 80) { sp1 = 20.0; sp2 = 97.0; }
    if (speed <= 60) { sp1 = 15.0; sp2 = 182.0; }
    if (speed <= 40) { sp1 = 10.0; sp2 = 273.0; }
    if (speed <= 20) { sp1 = 5.0; sp2 = 400.0; }
    if (speed == 0) { sp1 = 1.0; sp2 = 30000.0; }

/* we moved !!! */
    if (last != myloc) {
    /* new branch, same tree, just move up and down */
	if (last->root == myloc->root) {
	    if (dtype == 0)
		zs = (myloc->z - last->z) / sp1;
	    else
		zs = (myloc->z - myz) / sp1;
	    zls = (myloc->z - last->z) / sp1;
	    xls = (myloc->x - last->x) / sp1;
	    yls = (myloc->y - last->y) / sp1;
	    for (f = sp1; f >= 0.0; f -= 1.0) {
		myz += zs;
		[self lookAt:myloc->x - xls * f y:myloc->y - yls * f z:myloc->z - zls * f];
		[self display];
		usleep(20000);
	    }
	} else {		/* new branch AND tree, fly over there ... */
	/* rise into the sky */
	    xls = chi;
	    yls = theta;
	    zls = myz;
	    if (dtype == 0)
		myz = 40.0;
	    [self lookAt:myloc->x y:myloc->y z:myloc->z];
	    sp1 *= 2.0;
	    xls = (xls - chi) / sp1;
	    yls = (yls - theta) / sp1;
	    zls = (40.0 - zls) / sp1;
	    xs = chi;
	    ys = theta;
	    for (f = sp1; f >= 0.0; f -= 1.0) {
		chi = xs - xls * f;
		theta = ys - yls * f;
		if (dtype == 0)
		    myz = 40.0 - zls * f;
		[self setView:chi alt:theta aov:0.8];
		[self display];
		usleep(20000);
	    }

	/* zoom over to the new tree */
	    xs = (myloc->x - 3.0 - last->x);
	    ys = (myloc->y - 3.0 - last->y);
	    if (dtype == 0)
		zs = (myloc->z - 40.0);
	    else
		zs = (myloc->z - last->z);
	    xls = sqrt(SQR(xs) + SQR(ys)) / sp2;
	    if (xls > (float)(3 * speed))
		xls /= 4.0;
	    for (f = xls; f >= 0.0; f -= 1.0) {
		myx = myloc->x - 2.0 - xs / xls * f;
		myy = myloc->y - 2.0 - ys / xls * f;
		myz = myloc->z - zs / xls * f;

		[self lookAt:myloc->x y:myloc->y z:myloc->z];
		[self display];
		usleep(20000);
	    }
	    myx = myloc->x - 2.0;
	    myy = myloc->y - 2.0;
	    myz = myloc->z;
	}
    }
/* make sure we're looking at the right place */
    [self lookAt:myloc->x y:myloc->y z:myloc->z];
    [self display];

    last = myloc;

    return self;
}

/* preferences are currently keyboard selected */
- preferences:sender
{
    return self;
}

/* return a help string for display */
- (char *)help:window :browser
{
    return ("TreeView - Steve Ludtke  May 1992\n\nThis is the original view of gopherspace. This view has 2 modes. In planar mode, a grid is displayed around the observer at z=0 and lines are drawn from the ground to the 1st node.\
In 3d mode the grid and the trunk of the trees is not drawn. This is better for Coord's that span all 3 dimensions. Select the view and press 1 for planar\
 mode and 3 for 3d mode. Movement also occurs somewhat differently in the two modes. You may use the i,j,k,m,a, and z keys to move around. The sun is in the east.\n");
}

@end
