/* 3DView.m   Copyright 1992 Steve Ludtke */
/* This view allows the display of molecules with real time rotation */
/* The quick mode is handled in drawPS:: */

#import "Mtypes.h"
#import "D3View.h"
#import "MolObj.h"
#import "MolShape.h"
#import "SelectView.h"
#import <appkit/appkit.h>
#import <dpsclient/event.h>
#import <dpsclient/psops.h>
#import <stdio.h>
#import <math.h>
#include <libc.h>
#include <sys/file.h>

void PSsethel();	/* makes Helvetica current font */
int comp(struct DSORT *a,struct DSORT *b);

#define LRAD 10.0

extern id NXApp;

@implementation D3View
-initFrame:(NXRect *)myrect
{
char s[120];
RtPoint lFromP = {5.0,10.0,5.0},lToP = {0,0,0};
int i;
id aShader;

fromP[0]=fromP[1]=0; fromP[2]=60.0;
toP[0]=toP[1]=toP[2]=0.0; 

bpath=(char *)[[NXBundle mainBundle] directory];

[super initFrame:(NXRect *)myrect];

/* set up light sources, etc... */
[self setBackgroundColor:NX_COLORWHITE];  
[self setDrawBackgroundColor:YES];

	fromP[2]=60.0;
	[self setEyeAt:fromP toward:toP roll:0.0];
  
  aShader=[[N3DShader alloc] init];
  [(N3DShader *)aShader setShader:"matte"];

shape=[[MolShape alloc] init];
[(N3DShape *) shape  setShader:aShader];
[shape scale:-1.0 :1.0 :1.0];
[shape setBSSR:2.0 :0.1];
[[self setWorldShape:shape] free];


  ambLight=[[N3DLight alloc] init];
  [ambLight makeAmbientWithIntensity:0.4];
  [self addLight:ambLight];


ltheta=.785;
lchi=-.785;
aLight=[[N3DLight alloc] init];
lFromP[0]= LRAD*sin(lchi)*cos(ltheta);
lFromP[2]= LRAD*cos(lchi)*cos(ltheta);
lFromP[1]= LRAD*sin(ltheta);
[aLight makeDistantFrom:lFromP to:lToP intensity:1.0];
[self addLight:aLight];

Mmode=0;
Rmode=4;		/* start in "quick" mode */
useColor=0;

/* start spinning, all angles in radians */
chi=phi=0;
theta=0;
/*dchi=0.025;*/
dchi=dtheta=0.0;
initflag=1;
Rflags=0;

/* printer setup */
[[[NXApp printInfo] setVertCentered:YES] setOrientation:NX_PORTRAIT andAdjust:YES];
[[NXApp printInfo] setMarginLeft:0.0 right:0.0 top:0.0 bottom:0.0];
[[NXApp printInfo] setHorizPagination:NX_FITPAGINATION];
[[NXApp printInfo] setVertPagination:NX_FITPAGINATION];

busy=0;

/* images for spinning icon */
for (i=0; i<8; i++) {
	sprintf(s,"%s/icon%d.tiff",bpath,i);
	AIMGS[i]=[[NXImage alloc] initFromFile:s];
}

timer=(void *)
	DPSAddTimedEntry(TIMESTEP,(void *)itstime,self,NX_RUNMODALTHRESHOLD);

minshape=0;
return self;
}

- appDidInit: sender
{
AICV=[[NXApp appIcon] contentView];
AIMG=[NXImage findImageNamed: "NXAppTile"];
Ior1.x=Ior1.y=0.0;
Ior2.x=Ior2.y=8.0;

animicon=0;

[self setSurfaceTypeForAll:N3D_WireFrame chooseHider:YES];
[self zoom:self];
initflag=0; 

return self;
}

-setData:(Molecule *)Mol :(int)Nmol :(struct ELINFO *)Elinfo
{
mol=Mol;
nmol=Nmol;
elinfo=Elinfo;
[self zoom:self];
return self;
}


/* fix the scaling after being resized */
-superviewSizeChanged:(const NXSize *)oldsize
{
[super superviewSizeChanged:oldsize];
[[winSize cellAt:0 :0] setFloatValue:frame.size.width];
[[winSize cellAt:0 :1] setFloatValue:frame.size.height];
[self zoom:self];
return self;
}

-free
{
DPSRemoveTimedEntry(timer);
[super free];
return self;
}

-(BOOL)canPrintRIB
{
if (Rmode==4) return NO;
return YES;
}

-createQuick:(int)n
{
FILE *out;
char s[180];

out=fopen("/tmp/el.rib","w");
fprintf(out, "Display \"%s/Library/MolViewer/el%02d.tiff\" \"file\" \"rgba\"\n", (char *)getenv("HOME"),n);
fprintf(out,"##RenderMan RIB-Structure 1.0\n##System NeXT Release 3\nFrameBegin 1\n#\nFormat 100 100 1.000000\n\nScreenWindow -1 1 -1 1\n");
fprintf(out,"Projection \"perspective\" \"fov\" [ 40 ]\nTransform [ -1 0 0 0\n 0 1 0 0 0 0 -1 0 0 0 3.269231 1 ]\nWorldBegin\nTransformBegin\nConcatTransform [ 1 0 0 0\n 0 1 0 0 0 0 1 0 0 0 0 1 ]\n");
fprintf(out,"LightSource \"Light0x15178c\" \"distantlight\" \"intensity\" [ .95 ] \"lightcolor\" [ 1 1 1 ] \"from\" [ -4.999999 7.068252 5.003982 ] \"to\" [ 0 0 0 ]\n");
fprintf(out,"TransformEnd\nTransformBegin\nConcatTransform [ 1 0 0 0\n 0 1 0 0 0 0 1 0 0 0 0 1 ]\nLightSource \"Light0x14dbdc\" \"ambientlight\" \"intensity\" [ .3 ] \"lightcolor\" [ 1 1 1 ]\n");
fprintf(out,"TransformEnd\nShadingRate 1\nAttributeBegin\nColor [ 1 1 1 ]\nDeclare \"Ka\" \"float\"\nDeclare \"Kd\" \"float\"\n#\nSurface \"matte\"  \"Ka\" 1 \"Kd\" 1\nShadingInterpolation \"smooth\"\n");
fprintf(out,"GeometricRepresentation \"primitive\"\nTransformBegin\nConcatTransform [ -1 0 0 0\n 0 1 0 0 0 0 1 0 0 0 0 1 ]\n");
fprintf(out,"Surface \"plastic\"\nColor [ %f %f %f ]\nSphere %f %f %f 360\n",elinfo[n].color[0],elinfo[n].color[1],elinfo[n].color[2],elinfo[n].arad,-elinfo[n].arad,elinfo[n].arad);
fprintf(out,"TransformEnd\nAttributeEnd\nWorldEnd\nFrameEnd\n");
fclose(out);

sprintf(s,"Rendering %c%c. Please be patient.", elinfo[n].name[0],elinfo[n].name[1]);
[controller error:s :NO];
system("/usr/prman/prman /tmp/el.rib");
unlink("/tmp/el.rib");
return self;
}

/* This is called after QRM has finished rendering. In quick mode */
/* nothing is rendered. It's all done here */
-drawPS:(NXRect *)rects :(int)rectCount
{
char s[80];
float mx[9];
int i,j,k,t;
float tx,ty,tz,sca,xc,yc,xw,yw,mp[3];
static struct DSORT *dsort=NULL;
static int dsmax=1000000;
NXPoint point;
NXSize size;

if (mol==NULL) return self;
PSsethel();
if (Rmode==4) { 
/* ok, since the QRman interface has a bug, here we'll do some quick stuff */
/* with roughly the same effect (this is also much faster for space-filling) */
	if (mol[0].numa>dsmax) { free(dsort); dsort=NULL; }
/* dsort structure holds drawing information. For space filling mode it */
/* is actually sorted. In stick mode it just holds the transformed */
/* molecule coordinates */
	if (dsort==NULL) {
		dsort=malloc(sizeof(struct DSORT)*(mol[0].numa+1));
		dsmax=mol[0].numa;
	}

	PSsetlinewidth(0.0);
	PSsetgray(1.0);
	NXRectFill(&bounds);

	PSsetgray(NX_BLACK);

/* a transform that almost matches the QRM transformation */
	chi*=-1.0;
	phi*=-1.0;
	theta*=-1.0;
	mx[0]=cos(chi)*cos(phi)-cos(theta)*sin(phi)*sin(chi);
	mx[1]=cos(chi)*sin(phi)+cos(theta)*cos(phi)*sin(chi);
	mx[2]=sin(chi)*sin(theta);
	mx[3]= -sin(chi)*cos(phi)-cos(theta)*sin(phi)*cos(chi);
	mx[4]= -sin(chi)*sin(phi)+cos(theta)*cos(phi)*cos(chi);
	mx[5]=cos(chi)*sin(theta);
	mx[6]=sin(theta)*sin(phi);
	mx[7]= -sin(theta)*cos(phi);
	mx[8]=cos(theta);
	chi*=-1.0;
	phi*=-1.0;
	theta*=-1.0;	

/* scaling info */
	xw=bounds.size.width;
	yw=bounds.size.height;
	xc=bounds.size.width/2.0;
	yc=bounds.size.height/2.0;
	sca=bounds.size.height*1.8;

/* transform the coordinates -> DSORT struct */
	for (i=0; i<mol[0].numa; i++) {
		tx=mol[0].coord[i][0];
		ty= -mol[0].coord[i][2];
		tz=mol[0].coord[i][1];

		dsort[i].c[1]=tx*mx[3]+ty*mx[4]+tz*mx[5]+fromP[2];
		if (dsort[i].c[1]<=0.0) continue;
		dsort[i].c[0]=(tx*mx[0]+ty*mx[1]+tz*mx[2]+fromP[0])*sca/dsort[i].c[1]+xc;
		dsort[i].c[2]=(tx*mx[6]+ty*mx[7]+tz*mx[8]-fromP[1])*sca/dsort[i].c[1]+yc;
		dsort[i].anum=mol[0].atom[i].anum;
		dsort[i].atom=i;
		dsort[i].sel=mol[0].atom[i].sel;
		dsort[i].lab=mol[0].atom[i].lab;
		if (Rflags==0&&(dsort[i].c[0]<0 ||dsort[i].c[0]>xw||dsort[i].c[2]<0|| dsort[i].c[2]>yw)) dsort[i].c[0]=-1.0;
	}

/* stick mode. Use user paths for speed */
	if (Rflags==0) {
		pathc=0;
		for (i=0; i<mol[0].numlb; i++) {
			j=mol[0].lb[i].n1;
			k=mol[0].lb[i].n2;
			if (dsort[j].c[0]==-1.0||dsort[k].c[0]==-1.0) continue;
			path[pathc*2]=dsort[j].c[0];
			path[pathc*2+1]=dsort[j].c[2];
			path[pathc*2+2]=dsort[k].c[0];
			path[pathc*2+3]=dsort[k].c[2];
			ops[pathc]=dps_moveto;
			ops[pathc+1]=dps_lineto;
			pathc+=2;
		if (pathc>=1400) { 
			DPSDoUserPath(path, pathc * 2, dps_float, ops, pathc, &bounds, 
				dps_ustroke);
			pathc=0;
		}
		}
		if (pathc!=0) DPSDoUserPath(path, pathc * 2, dps_float, ops, pathc, 
				&bounds, dps_ustroke);

		sca*=1.05;
		sca/=fromP[2];		
		/* circle selected atoms */
		for (i=0; i<mol[0].numa; i++) {
			if (dsort[i].lab) {
				PSsetgray(0.0);
				PSmoveto(dsort[i].c[0]+2.0,dsort[i].c[2]-3.0);
				sprintf(s,"%c%c%1d",elinfo[dsort[i].anum].name[0], elinfo[dsort[i].anum].name[1],dsort[i].atom);
				PSshow(s);
				PSstroke();
			}
			if (dsort[i].sel) {
				PSsetgray(0.0);
				PSmoveto(dsort[i].c[0]+sca/3.0,dsort[i].c[2]);
				PSarc(dsort[i].c[0],dsort[i].c[2],sca/3.0,0.0,360.0);
				PSstroke();
			}
		}
	}
/* ball & stick is stick mode with colors & double bonds */
	else if (Rflags==1) {
		for (i=0; i<mol[0].numlb; i++) {
			j=mol[0].lb[i].n1;
			k=mol[0].lb[i].n2;
			t=mol[0].lb[i].type;
			if (dsort[j].c[0]==-1.0||dsort[k].c[0]==-1.0) continue;

			mp[0]=(dsort[j].c[0]+dsort[k].c[0])/2.0;
			mp[2]=(dsort[j].c[2]+dsort[k].c[2])/2.0;
			if (t=='D') PSsetlinewidth(4.0);
			else PSsetlinewidth(1.0);
			PSsetrgbcolor(elinfo[dsort[j].anum].color[0], elinfo[dsort[j].anum].color[1],elinfo[dsort[j].anum].color[2]);
			PSmoveto(dsort[j].c[0],dsort[j].c[2]);
			PSlineto(mp[0],mp[2]);
			PSstroke();
			PSsetrgbcolor(elinfo[dsort[k].anum].color[0], elinfo[dsort[k].anum].color[1],elinfo[dsort[k].anum].color[2]);
			PSmoveto(dsort[k].c[0],dsort[k].c[2]);
			PSlineto(mp[0],mp[2]);
			PSstroke();
		
		}

		sca*=1.05;
		sca/=fromP[2];		
		/* circle selected atoms */
		for (i=0; i<mol[0].numa; i++) {
			if (dsort[i].lab) {
				PSsetgray(0.0);
				PSmoveto(dsort[i].c[0]+2.0,dsort[i].c[2]);
				sprintf(s,"%c%c%1d",elinfo[dsort[i].anum].name[0], elinfo[dsort[i].anum].name[1],dsort[i].atom);
				PSshow(s);
				PSstroke();
			}
			if (dsort[i].sel) {
				PSsetgray(0.0);
				PSmoveto(dsort[i].c[0]+sca/3.0,dsort[i].c[2]);
				PSarc(dsort[i].c[0],dsort[i].c[2],sca/3.0,0.0,360.0);
				PSstroke();
			}
		}
	}
/* Space filling mode */
	else {
		sca*=1.05;
		sca*=(elinfo[0].rad/elinfo[0].arad);
		sca/=fromP[2];		
		size.height=size.width=floor(sca)*2.0;

		/* scale the pre-rendered images. I tried using RIB images */
		/* here, but it gave all sorts of errors. NeXT said that it */
		/* should work. Maybe next release ... */
		for (i=0; i<16; i++) {
			if (elinfo[i].image==nil) continue;
			[elinfo[i].image setSize:&size];
		}
		/* Sort for painters algorithm */
		qsort(dsort,mol[0].numa,sizeof(struct DSORT),(void *)comp);
		
		/* composite images */
		for (i=0; i<mol[0].numa; i++) {
			if (dsort[i].c[1]<0.0) continue;
			j=dsort[i].anum;
			point.x=dsort[i].c[0]-sca;
			point.y=dsort[i].c[2]-sca;
			[elinfo[j].image composite:NX_SOVER toPoint:&point];
			/* label atoms with label flag */
			if (dsort[i].lab) {
				PSsetgray(0.5);
				PSmoveto(dsort[i].c[0]+2.0,dsort[i].c[2]);
				sprintf(s,"%c%c%1d",elinfo[dsort[i].anum].name[0], elinfo[dsort[i].anum].name[1],dsort[i].atom+1);
				PSshow(s);
				PSstroke();
			}
			/* put a dot on selected atoms (or circle if H) */
			if (dsort[i].sel) {
				PSmoveto(dsort[i].c[0]+sca/3.0,dsort[i].c[2]);
				PSarc(dsort[i].c[0],dsort[i].c[2],sca/3.0,0.0,360.0);
				if (j==0) {
					PSsetgray(0.0);
					PSstroke();
				} else {
					PSsetgray(1.0);
					PSfill();
				}
			}
		}
	}
}		

if (bounds.size.width<150) { busy=0; return self; }
/* display alt and az in upper right corner */
PSsetgray(0.0);
sprintf(s,"Alt:%6.2f  Az:%6.2f",theta*57.295787,chi*57.295787);
PSmoveto(bounds.size.width-100.0,bounds.size.height-15.0);
PSshow(s);
sprintf(s,"Psi:%6.2f",phi/DRC);
PSmoveto(bounds.size.width-60.0,bounds.size.height-28.0);
PSshow(s);
PSstroke();

busy=0;
return self;
}	

/* recalculates and redisplays data */
-zoom:sender
{
[shape setData:mol :nmol :elinfo :Rmode :Rflags];
[shape setAng:theta :chi :phi];
[self display];

return self;
}

/* obvious */
-setAng:(float)az :(float)alt :(float)Phi
{
theta=alt;
chi=az;
phi=Phi;
return self;
}

-setMmode:sender
{
Mmode=[sender selectedCol];
return self;
}

-zeroRot:sender
{
dtheta=dchi=theta=chi=phi=0.0;
[[mangS cellAt:0 :0] setFloatValue:theta/DRC];
[[mangS cellAt:1 :0] setFloatValue:chi/DRC];
[[mangS cellAt:2 :0] setFloatValue:phi/DRC];
[[mangT cellAt:0 :0] setFloatValue:theta/DRC];
[[mangT cellAt:1 :0] setFloatValue:chi/DRC];
[[mangT cellAt:2 :0] setFloatValue:phi/DRC];
[self display];
return self;
}

-zeroXlate:sender
{
fromP[0]=fromP[1]=0.0;
toP[0]=toP[1]=0.0;
[self setEyeAt:fromP toward:toP roll:0.0];
[self display];
return self;
}

-deSel:sender
{
[selView deselectAll];
[selView notifyO];
return self;
}

-selectAt:(float)x :(float)y :(int) flag
{
float mx[9];
int i,s= -1;
float tx,ty,tz,sca,xc,yc,xw,yw,x2,y2,z2,d=1000.0,d2;

/* a transform that almost matches the QRM transformation */
	chi*=-1.0;
	phi*=-1.0;
	theta*=-1.0;
	mx[0]=cos(chi)*cos(phi)-cos(theta)*sin(phi)*sin(chi);
	mx[1]=cos(chi)*sin(phi)+cos(theta)*cos(phi)*sin(chi);
	mx[2]=sin(chi)*sin(theta);
	mx[3]= -sin(chi)*cos(phi)-cos(theta)*sin(phi)*cos(chi);
	mx[4]= -sin(chi)*sin(phi)+cos(theta)*cos(phi)*cos(chi);
	mx[5]=cos(chi)*sin(theta);
	mx[6]=sin(theta)*sin(phi);
	mx[7]= -sin(theta)*cos(phi);
	mx[8]=cos(theta);
	chi*=-1.0;
	phi*=-1.0;
	theta*=-1.0;	

/* scaling info */
	xw=bounds.size.width;
	yw=bounds.size.height;
	xc=bounds.size.width/2.0;
	yc=bounds.size.height/2.0;
	sca=bounds.size.height*1.8;

/* transform the coordinates */
	for (i=0; i<mol[0].numa; i++) {
		tx=mol[0].coord[i][0];
		ty= -mol[0].coord[i][2];
		tz=mol[0].coord[i][1];

		y2=tx*mx[3]+ty*mx[4]+tz*mx[5]+fromP[2];
		if (y2<=0.0) continue;
		x2=(tx*mx[0]+ty*mx[1]+tz*mx[2]+fromP[0])*sca/y2+xc;
		z2=(tx*mx[6]+ty*mx[7]+tz*mx[8]-fromP[1])*sca/y2+yc;
		d2=sqrt(SQR(x2-x)+SQR(z2-y));
		if (d2<10.0 && d2<d) { s=i; d=d2; }
/*		printf("%d   %f %f %f %f %f\n",i,x,x2,y,y2,d2);*/
	}
if (s==-1) return self;
if (!flag) [selView deselectAll];
[selView select:s];

return self;
}

/* allows spinning and zooming with the mouse */
-mouseDown:(NXEvent *)oevent 
{
int oldMask,loop=1;
float ix=0,iy=0,ix1=0,iy1=0,ix2=0,iy2=0,ix3=0,iy3=0;
NXEvent *event,evs;
long tm,tm2;

evs=*oevent;
oevent=&evs;
[self convertPoint:&oevent->location fromView:nil];
ix2=ix=oevent->location.x;
iy2=iy=oevent->location.y;
tm2=tm=oevent->time;

oldMask = [window addToEventMask:NX_LMOUSEDRAGGEDMASK];

while (loop) {
    event = [NXApp getNextEvent:(NX_LMOUSEUPMASK | NX_LMOUSEDRAGGEDMASK)];
    [self convertPoint:&event->location fromView:nil];
/*	[shape setDrawAsBox:YES];*/
        switch (event->type) {
        case NX_LMOUSEUP:
            loop = 0;
			if (Mmode==0) {
 				if (event->time-tm2==0) tm2=event->time-1;
	        	dchi=(event->location.x-ix2)/(float)(event->time-tm2)/10.0;
				dtheta=-(event->location.y-iy2)/(float)(event->time-tm2)/10.0;
				if (fabs(dchi)<.004) dchi=0.0;
				[shape setAng:theta :chi :phi];
                [self display];
				[[mangS cellAt:0 :0] setFloatValue:theta/DRC];
				[[mangS cellAt:1 :0] setFloatValue:chi/DRC];
				[[mangT cellAt:0 :0] setFloatValue:theta/DRC];
				[[mangT cellAt:1 :0] setFloatValue:chi/DRC];
			}
			else if (Mmode==1) {
			}
			else if (Mmode==2) {
				if (event->flags&NX_SHIFTMASK) 
					[self selectAt:event->location.x :event->location.y :1];
				else [self selectAt:event->location.x :event->location.y :0];
			}
            break;
        case NX_LMOUSEDRAGGED:
			if (Mmode==0) {
		        theta-=(event->location.y-iy)/30.0;
		        chi+=(event->location.x-ix)/30.0;
		        if (theta>M_PI) theta-=2.0*M_PI;
				if (theta<-M_PI) theta+=2.0*M_PI;
		        while (chi>2.0*M_PI) chi-=2.0*M_PI;
		        while (chi<0.0) chi+=2.0*M_PI;
		        ix2=ix;
		        iy2=iy;
		        tm2=tm;
		        ix=event->location.x;
		        iy=event->location.y;
		        tm=event->time;
				[shape setAng:theta :chi :phi];
		        [self display];
			}
			else if (Mmode==1) {
				toP[0]=fromP[0]+=(event->location.x-ix)/25.0;
				toP[1]=fromP[1]-=(event->location.y-iy)/25.0;
		        ix=event->location.x;
		        iy=event->location.y;
				[self setEyeAt:fromP toward:toP roll:0.0];
				[self display];		
			}
            break;
        default:
            break;
        }
}
[window setEventMask:oldMask];
return self;
}

/* function called by timer */
void itstime(DPSTimedEntry entry,double now,id call)
{
[call step];
return;
}

/* do one time step */
-step
{
if (initflag) { 
	/* first time, do initialization stuff */
}
/* This animates the icon */
if (animicon>=0 && (dchi!=0 || dtheta!=0)) {
	[AICV lockFocus];
	[AIMG composite:NX_SOVER toPoint:&Ior1];								                       	
	[AIMGS[animicon] composite:NX_SOVER toPoint:&Ior2];								                       	
	[AICV unlockFocus];
	[AICV display];
	animicon++;
	if (animicon>7) animicon=0;
}

if (minshape) {
	[controller minStep:self];
	if (dchi==0.0) {
		[self display];
		return self;
	}
}
if (dchi==0.0&&dtheta==0.0) return self;
chi+=dchi;
theta+=dtheta;
if (theta>M_PI) theta-=2.0*M_PI;
if (theta<-M_PI) theta+=2.0*M_PI;
while (chi>2.0*M_PI) chi-=2.0*M_PI;
while (chi<0.0) chi+=2.0*M_PI;
if (busy) {
	return self;
}
[shape setAng:theta :chi :phi];
[[mangS cellAt:0 :0] setFloatValue:theta/DRC];
[[mangS cellAt:1 :0] setFloatValue:chi/DRC];
[[mangS cellAt:2 :0] setFloatValue:phi/DRC];
[[mangS cellAt:3 :0] setFloatValue:fromP[2]];
[[mangT cellAt:0 :0] setFloatValue:theta/DRC];
[[mangT cellAt:1 :0] setFloatValue:chi/DRC];
[[mangT cellAt:2 :0] setFloatValue:phi/DRC];
[[mangT cellAt:3 :0] setFloatValue:fromP[2]];
[self display];
return self;
}

-(int)acceptsFirstMouse { return (YES); }

-setcontroller:con 
{
controller=con;
return self;
}

/* allow user to pause spinning (not used)*/
-togFreeze:sender
{
static float Tdchi;

if ([sender intValue]) {
	Tdchi=dchi;
	dchi=0.0;
	return self;
}
dchi=Tdchi;
return self;
}

/* just set a few things before MolShape takes over */
- renderSelf:(RtToken)context
{
RtFloat par[2]= {8.0,8.0};
/*RiErrorIgnore();*/

RiShadingRate(2.0);
RiGeometricApproximation(RI_TESSELATION,RI_PARAMETRIC,par,RI_NULL);
busy=1;
return self;
}

/* dump the current screen image as a RIB file */
- dumpRib:sender
{
  static id savePanel=nil;
  NXStream *ts;
  char buf[MAXPATHLEN+1];
  int omode;

  if (!savePanel) {
    savePanel=[SavePanel new];
    [savePanel setRequiredFileType:"rib"];
  }

  if([savePanel runModal]){
    ts=NXOpenMemory(NULL, 0, NX_WRITEONLY);
    strcpy(buf, [savePanel filename]);
    strrchr(buf,'.')[0]='\0';
    NXPrintf(ts, "Display \"%s.tiff\" \"file\" \"rgb\"\n", buf);
	omode=Rmode;
	Rmode=3;
/* the Rmode+8 causes a white rectangle to appear behind the molecule */
	[shape setData:mol :nmol :elinfo :Rmode+8 :Rflags];
    [self copyRIBCode:ts];
	Rmode=omode;
	[shape setData:mol :nmol :elinfo :Rmode :Rflags];
    NXSaveToFile(ts, [savePanel filename]);
    NXCloseMemory(ts,NX_FREEBUFFER);
  }
  return self;
}

- setAmbLight:sender
{
[ambLight setIntensity:[sender floatValue]];
[self display];
return self;
}

- setLight:sender
{
[aLight setIntensity:[sender floatValue]];
[self display];
return self;
}

-setLightX:sender
{
RtPoint from;
lchi= -[sender floatValue];
from[0]= LRAD*sin(lchi)*cos(ltheta);
from[2]= LRAD*cos(lchi)*cos(ltheta);
from[1]= LRAD*sin(ltheta);
[aLight setFrom:from];
[self display];
return self;
}

-setLightY:sender
{
RtPoint from;
ltheta=[sender floatValue];
from[0]= LRAD*sin(lchi)*cos(ltheta);
from[2]= LRAD*cos(lchi)*cos(ltheta);
from[1]= LRAD*sin(ltheta);
[aLight setFrom:from];
[self display];
return self;
}

/* rendering mode - ie. point,line,surface,smooth,quick ... */
- setMode:sender
{
int i;

Rmode=i=[sender selectedRow];

[shape setData:mol :nmol :elinfo :Rmode :Rflags];
switch(i) {
case 0:
 [self setSurfaceTypeForAll:N3D_PointCloud chooseHider:YES];
 if (!useColor) [window setDepthLimit:NX_TwoBitGrayDepth];
break;
case 1:
 [self setSurfaceTypeForAll:N3D_WireFrame chooseHider:YES];
 if (!useColor) [window setDepthLimit:NX_TwoBitGrayDepth];
break;
case 2:
 [self setSurfaceTypeForAll:N3D_FacetedSolids chooseHider:YES];
break;
case 3:
 [self setSurfaceTypeForAll:N3D_SmoothSolids chooseHider:YES];
break;
case 4:
 [self setSurfaceTypeForAll:N3D_WireFrame chooseHider:NO];
 [self setHider:N3D_NoRendering];
 if (!useColor) [window setDepthLimit:NX_TwoBitGrayDepth];
break;
}
[self display];
return self;
}

-printPS:sender
{
id image;
int fd;
NXStream *out;

[shape setData:mol :nmol :elinfo :Rmode+8 :Rflags];
if (Rflags==2&&Rmode==4) {
	[self lockFocus];
	image=[[NXBitmapImageRep alloc] initData:NULL fromRect:&bounds];
	[self unlockFocus];
	out=NXOpenFile(fd=open("/tmp/MVprint.tiff",O_WRONLY|O_CREAT,0777),NX_WRITEONLY);
	[image writeTIFF:out];
	NXClose(out);
	close(fd);
	[image free];
	system("open /tmp/MVprint.tiff");
}
else [super printPSCode:sender];
[shape setData:mol :nmol :elinfo :Rmode :Rflags];
return self;
}

/* mode, ie. stick, ball&stick or spacefill */
-setFlags:sender
{
int i;
Rflags=0;
if ([[flagSel findCellWithTag:1] intValue]) Rflags+=1;
if ([[flagSel findCellWithTag:2] intValue]) Rflags+=2;
if (Rflags==2&&Rmode==4) {
	for (i=0; i<40; i++) if (elinfo[i].image!=nil) break;
	if (i==40) [controller error:"Warning: you must render some atoms with 'make quick' on the preferences panel and restart before this mode will work" :NO];
}
if ([chgCol intValue]) Rflags+=4;
[shape setData:mol :nmol :elinfo :Rmode :Rflags];
[self display];
return self;
}

/* dist form observer to object */
-setEDist:sender
{
fromP[2]= -[sender floatValue];
[self setEyeAt:fromP toward:toP roll:0.0];
[self display];
return self;
}

/* generate a sequence of RIB's as an animation */
- ribRot:sender
{
static id savePanel=nil;
NXStream *ts;
float ophi,step,start;
char buf[MAXPATHLEN+1],s[20];
int i,omode,j;

if (savePanel==nil) savePanel=[SavePanel new];
[savePanel setRequiredFileType:"rib"];

j=[[animSpin cellAt:1 :0] intValue];
step=2.0*M_PI/(float)j;
start=[[animSpin cellAt:0 :0] floatValue];

ophi=phi;
omode=Rmode;
if([savePanel runModal]){
/* if you want to change the number of frames in the animation, change */
/* the next 2 lines */
	for (i=0; i<j; i++) {
		phi=(float)i*step+start;
		[shape setAng:theta :chi :phi];
		strcpy(buf, [savePanel filename]);
		strrchr(buf,'.')[0]='\0';
		ts=NXOpenMemory(NULL, 0, NX_WRITEONLY);
		NXPrintf(ts, "Display \"%s.%d.tiff\" \"file\" \"rgb\"\n", buf,i);
		Rmode=3;
		[shape setData:mol :nmol :elinfo :Rmode :Rflags]; 
		[self copyRIBCode:ts];
		Rmode=omode;
		[shape setData:mol :nmol :elinfo :Rmode :Rflags];
		[self display];
		sprintf(s,".%d.rib",i);
		strcat(buf,s);
		NXSaveToFile(ts, buf);
		NXCloseMemory(ts,NX_FREEBUFFER);
	}
}
phi=ophi;
[shape setAng:theta :chi :phi];
[self display];
return self;
}

/* generate a sequence of RIB's as an animation */
- ribConform:sender
{
static id savePanel=nil;
NXStream *ts;
float ophi;
char buf[MAXPATHLEN+1],s[20];
int i,omode;
float x,y;

if (savePanel==nil) savePanel=[SavePanel new];
[savePanel setRequiredFileType:"rib"];
  
ophi=phi;
omode=Rmode;
if([savePanel runModal]){
/* if you want to change the number of frames in the animation, change */
/* the next 2 lines */
	for (i=1; i<=60; i+=1) {
		x=cos((float)i*.1047+.785)*180.0*(float)(65-i)/65.0;
		y=sin((float)i*.1047+.785)*180.0*(float)(65-i)/65.0;
		[controller setPhi:x];
		[controller setPsi:y];
		strcpy(buf, [savePanel filename]);
		strrchr(buf,'.')[0]='\0';
		ts=NXOpenMemory(NULL, 0, NX_WRITEONLY);
		NXPrintf(ts, "Display \"%s.%d.tiff\" \"file\" \"rgb\"\n", buf,i);
		Rmode=3;
		[shape setData:mol :nmol :elinfo :Rmode :Rflags]; 
		[self copyRIBCode:ts];
		Rmode=omode;
		[shape setData:mol :nmol :elinfo :Rmode :Rflags];
		[self display];
		sprintf(s,".%d.rib",i);
		strcat(buf,s);
		NXSaveToFile(ts, buf);
		NXCloseMemory(ts,NX_FREEBUFFER);
	}
}
phi=ophi;
[shape setAng:theta :chi :phi];
[self display];
return self;
}

/* generate a sequence of RIB's as an animation */
- ribConfTo:sender
{
static id savePanel=nil;
NXStream *ts;
float ophi;
char buf[MAXPATHLEN+1],s[20];
int i,steps,omode;
float x,y,x2,y2;

if (savePanel==nil) savePanel=[SavePanel new];
[savePanel setRequiredFileType:"rib"];
  
ophi=phi;
omode=Rmode;
if([savePanel runModal]){
/* if you want to change the number of frames in the animation, change */
/* the next 2 lines */
	x=mol[0].am[1].phi/DRC;
	y=mol[0].am[1].psi/DRC;
	x2=[[animConf cellAt:0 :0] floatValue];
	y2=[[animConf cellAt:1 :0] floatValue];
	steps=[[animConf cellAt:2 :0] intValue];
	for (i=1; i<=steps; i++) {
		[controller setPhi:(x2-x)*(float)i/(float)steps+x];
		[controller setPsi:(y2-y)*(float)i/(float)steps+y];
		phi=(float)i*.15708;
		[shape setAng:theta :chi :phi];
		strcpy(buf, [savePanel filename]);
		strrchr(buf,'.')[0]='\0';
		ts=NXOpenMemory(NULL, 0, NX_WRITEONLY);
		NXPrintf(ts, "Display \"%s.%d.tiff\" \"file\" \"rgb\"\n", buf,i);
		Rmode=3;
		[shape setData:mol :nmol :elinfo :Rmode :2]; 
		[self copyRIBCode:ts];
		Rmode=omode;
		[shape setData:mol :nmol :elinfo :Rmode :Rflags];
		[self display];
		sprintf(s,".%d.rib",i);
		strcat(buf,s);
		NXSaveToFile(ts, buf);
		NXCloseMemory(ts,NX_FREEBUFFER);
	}
}
phi=ophi;
[shape setAng:theta :chi :phi];
[self display];
return self;
}

/* This used to dump a DKB raytracer format file. Now it dumps a POV */
/* (based on DKB) file. You can get POV from wuarchive.wustl.edu */
/* in /graphics/graphics/ray. POV is certainly slower than renderman, */
/* but it is much more flexible. */
-dkbDump:sender
{
static id savePanel=nil;
FILE *out;
char s[80];
int i,n;
float x,y,z,mx[9];

if (savePanel==nil) savePanel=[SavePanel new];
[savePanel setRequiredFileType:"pov"];
  
if([savePanel runModal]){
	sprintf(s,"cp %s/header.dat %s",bpath,[savePanel filename]);
	system(s);
	out=fopen([savePanel filename],"a");
	fprintf(out,"camera {\n    location <0 0 %f>\n",fromP[2]);
	fprintf(out,"    up <0 1 0>\n    right <1.33333 0 0>\n");
	fprintf(out,"    look_at <0 0 0>\n    sky <0 1 0>\n}\n");

	chi*=-1.0;
	phi*=-1.0;
	theta*=-1.0;
	mx[0]=cos(chi)*cos(phi)-cos(theta)*sin(phi)*sin(chi);
	mx[1]=cos(chi)*sin(phi)+cos(theta)*cos(phi)*sin(chi);
	mx[2]=sin(chi)*sin(theta);
	mx[3]= -sin(chi)*cos(phi)-cos(theta)*sin(phi)*cos(chi);
	mx[4]= -sin(chi)*sin(phi)+cos(theta)*cos(phi)*cos(chi);
	mx[5]=cos(chi)*sin(theta);
	mx[6]=sin(theta)*sin(phi);
	mx[7]= -sin(theta)*cos(phi);
	mx[8]=cos(theta);
	chi*=-1.0;
	phi*=-1.0;
	theta*=-1.0;	

	for (i=0; i<mol[0].numa; i++) {
		x=mol[0].coord[i][0];
		y=mol[0].coord[i][1];
		z=mol[0].coord[i][2];
		n=mol[0].atom[i].anum;

		fprintf(out,"object {\n    sphere { <%f %f %f> %f }\n", 
			-(x*mx[0]+y*mx[1]+z*mx[2]),x*mx[3]+y*mx[4]+z*mx[5],
			x*mx[6]+y*mx[7]+z*mx[8],elinfo[n].rad);

		fprintf(out,"    texture {\n\tcolor red %5.3f green %5.3f blue %5.3f\n\tphong 1.0\n    }\n}\n\n",elinfo[n].color[0], elinfo[n].color[1],elinfo[n].color[2]);

	}
	fclose(out);
}
return self;
}

-togMin:sender
{
if ([sender intValue]) minshape=1;
else minshape=0;

return self;
}

/* save the current scene as EPS */
- dumpEPS:sender
{
static id savePanel=nil;
NXStream *out;

if (!savePanel) {
    savePanel=[SavePanel new];
    [savePanel setRequiredFileType:"eps"];
}

if([savePanel runModal]){
	out=NXOpenMemory(NULL,0, NX_WRITEONLY);
	[self copyPSCodeInside:&bounds to:out];
	NXSaveToFile(out,[savePanel filename]);
	NXCloseMemory(out,NX_FREEBUFFER);
}
return self;
}

-setColor:sender
{
int i;

useColor=[sender intValue];
i=Rmode;

[self setSurfaceTypeForAll:N3D_PointCloud chooseHider:YES];
switch(i) {
case 0:
 [self setSurfaceTypeForAll:N3D_PointCloud chooseHider:YES];
 if (!useColor) [window setDepthLimit:NX_TwoBitGrayDepth];
break;
case 1:
 [self setSurfaceTypeForAll:N3D_WireFrame chooseHider:YES];
 if (!useColor) [window setDepthLimit:NX_TwoBitGrayDepth];
break;
case 2:
 [self setSurfaceTypeForAll:N3D_FacetedSolids chooseHider:YES];
break;
case 3:
 [self setSurfaceTypeForAll:N3D_SmoothSolids chooseHider:YES];
break;
case 4:
 [self setSurfaceTypeForAll:N3D_WireFrame chooseHider:NO];
 [self setHider:N3D_NoRendering];
 if (!useColor) [window setDepthLimit:NX_TwoBitGrayDepth];
break;
}
[self display];

return self;
}

-setAngT:sender
{
int i;

dchi=dtheta=0;

i=[sender selectedRow];
switch (i)
{
case 0:
	theta=[[mangT cellAt:0 :0] floatValue]*DRC;
	[[mangS cellAt:0 :0] setFloatValue:theta/DRC];
	break;
case 1:
	chi=[[mangT cellAt:1 :0] floatValue]*DRC;
	[[mangS cellAt:1 :0] setFloatValue:chi/DRC];
	break;
case 2:
	phi=[[mangT cellAt:2 :0] floatValue]*DRC;
	[[mangS cellAt:2 :0] setFloatValue:phi/DRC];
	break;
case 3:
	fromP[2]=[[mangT cellAt:3 :0] floatValue];
	[[mangS cellAt:3 :0] setFloatValue:fromP[2]];
	[self setEyeAt:fromP toward:toP roll:0.0];
	break;
}
[shape setAng:theta :chi :phi];
[self display];
return self;
}

-setAngS:sender
{
int i;

dchi=dtheta=0;

i=[sender selectedRow];
switch (i)
{
case 0:
	theta=[[mangS cellAt:0 :0] floatValue]*DRC;
	[[mangT cellAt:0 :0] setFloatValue:theta/DRC];
	break;
case 1:
	chi=[[mangS cellAt:1 :0] floatValue]*DRC;
	[[mangT cellAt:1 :0] setFloatValue:chi/DRC];
	break;
case 2:
	phi=[[mangS cellAt:2 :0] floatValue]*DRC;
	[[mangT cellAt:2 :0] setFloatValue:phi/DRC];
	break;
case 3:
	fromP[2]=[[mangS cellAt:3 :0] floatValue];
	[[mangT cellAt:3 :0] setFloatValue:fromP[2]];
	[self setEyeAt:fromP toward:toP roll:0.0];
	break;
}
[shape setAng:theta :chi :phi];
[self display];
return self;
}


/* NeXT said that this should clean up most of the QRM problems. */
/* As far as I can tell it doesn't have any effect */
- flushRIB
{
    RiSynchronize( RI_FLUSH );
    RiSynchronize( RI_WAIT );
    return self;
}

-newBSSR:sender
{
[shape setBSSR:[[sender cellAt:0 :0] floatValue] :[[sender cellAt:1 :0] floatValue]];
[self display];
return self;
}

int comp(struct DSORT *a,struct DSORT *b)
{
if (a->c[1]>b->c[1]) return(-1);
if (a->c[1]==b->c[1]) return(0);
else return(1);
}
@end
