
/*
 *  BSPLINE.C
 *
 *  Matthew Dillon.
 *  Public Domain (no Copyrights whatsoever)
 *
 *  -Assumes AZTEC compilation,  +L (32 bit ints), with all AMIGA symbols
 *  precompiled.  Additionally expects certain typedefs and routines
 *  found in MY.LIB, as well as some MY.LIB #include files.
 *
 *  An experienced programmer can remove the MY.LIB dependancies
 *  (openlibs() call), and figure out what typedefs have been assumed if
 *  he wishes to compile the program.  You can also simply extract the
 *  Bezier functions for your own use.
 *
 *  BSPLINE EQUATIONS:
 *
 *	c(t) = T(t)BP
 *
 *	T(t) = (t^3 t^2 t 1)		P= ( Pi-1 )
 *	B = (  -1   3  -3   1	)	   ( Pi   )
 *	    (	3  -6	3   0	)	   ( Pi+1 )
 *	    (  -3   0	3   0	)	   ( Pi+2 )
 *	    (	1   4	1   0	)
 *
 *	t:  range 0 to 1
 *	C:  coordinate matrix  1xD matrix (D = dimensions)
 *	B:  Bspline matrix     4x4
 *	P:  Ctrl. Point matrix 4xD matrix (D = dimensions)
 *
 *	using D = 2.  For B-Spline, must loop through all control points
 *	beginning at Pi+1 and ending at Pend-2
 */

#include <typedefs.h>
#include <xmisc.h>

#define     SHIFTS  9
#define     ONE     (1<<SHIFTS)
#define     MAXPTS  256 	/* Maximum # of bspline points allowed	*/

typedef struct PropInfo XPI;
typedef struct Image	IM;

extern IMESS *GetMsg();

#define MYGADGETS   (WINDOWSIZING|WINDOWDRAG|WINDOWDEPTH|WINDOWCLOSE)

NW Nw = {
    64, 64, 320, 100,
    0, 1,
    NEWSIZE|MOUSEBUTTONS|MOUSEMOVE|CLOSEWINDOW|GADGETDOWN|GADGETUP|MENUPICK,
    MYGADGETS|REPORTMOUSE|ACTIVATE|NOCAREREFRESH,
    0, 0, (UBYTE *)"Bspline, By Matthew Dillon", NULL, NULL,
    32, 64, -1, -1, WBENCHSCREEN
};

WIN *Win;
RP  *Rp;
short Ux, Uy, Lx, Ly;
short Step = 128;
short Display = 1;

short Pts[MAXPTS+1][2], Npts;

main(ac, av)
char *av[];
{
    register IMESS *mess;
    short notdone = 1;
    short pt = -1;
    char mode = 0;	    /* 0=move pts, 1=add pts, 2=del pts     */

    short gy, gg = 0;
    XPI *po;

    init_gadgets(&Nw, &po);
    exiterr(!openlibs(INTUITION_LIB|GRAPHICS_LIB), "unable to open libs");
    exiterr(!(Win = OpenWindow(&Nw)), "unable to open window");
    init_menu(Win);
    Rp = Win->RPort;
    SetAPen(Rp, 3);
    SetDrMd(Rp, COMPLEMENT);
    setpoint(Pts, 0, 32, 32);
    setpoint(Pts, 1, 40, 40);
    setpoint(Pts, 2, 100, 50);
    setpoint(Pts, 3, 200, 60);
    Npts = 4;
    setbounds(Pts, Npts);
    while (notdone) {
	short mx, my, mm = 0;
	WaitPort(Win->UserPort);
	while (mess = GetMsg(Win->UserPort)) {
	    switch(mess->Class) {
	    case CLOSEWINDOW:
		notdone = 0;
		break;
	    case NEWSIZE:
		setbounds(Pts, Npts);
		break;
	    case GADGETUP:
	    case GADGETDOWN:
		{
		    gg = mess->Class;
		    gy = po->VertPot / 256;
		}
		break;
	    case MOUSEBUTTONS:
		switch(mess->Code) {
		case SELECTDOWN:
		    pt = getpoint(Pts, Npts, mess->MouseX, mess->MouseY);
		    switch(mode) {
		    case 0:
			mm = 1;
			mx = mess->MouseX;
			my = mess->MouseY;
			goto break2;
		    case 1:
			if (pt < 0)
			    pt = 0;
			if (Npts != MAXPTS) {
			    bmov(Pts[pt], Pts[pt+1], (Npts-pt)*sizeof(Pts[0]));
			    Pts[pt][0] = mess->MouseX;
			    Pts[pt][1] = mess->MouseY;
			    ++Npts;
			}
			break;
		    case 2:
			if (pt >= 0 && pt < Npts) {
			    bmov(Pts[pt+1], Pts[pt], (Npts-pt)*sizeof(Pts[0]));
			    --Npts;
			}
			break;
		    }
		    clearall();
		    drawcurve(Pts, Npts);
		    drawpoints(Pts, 0, Npts);
		    break;
		case SELECTUP:
		    pt = -1;
		    break;
		}
break2:
		break;
	    case MENUPICK:
		switch(MENUNUM(mess->Code)) {
		case 0:
		    mode = ITEMNUM(mess->Code);
		    break;
		case 1:
		    drawpoints(Pts, 0, Npts);
		    Display = ITEMNUM(mess->Code);  /* 0=cross 1=nums	*/
		    drawpoints(Pts, 0, Npts);
		}
		break;
	    case MOUSEMOVE:
		if (gg == GADGETDOWN) {
		    gy = po->VertPot / 256;
		    break;
		}
		if (mode == 0 || mode == 1) {
		    mm = 1;
		    mx = mess->MouseX;
		    my = mess->MouseY;
		}
		break;
	    default:
		break;
	    }
	    ReplyMsg(mess);
	}
	if (mm && pt >= 0) {
	    register int i;
	    register int n;

	    i = (pt - 3 < 0) ? 0 : pt - 3;
	    n = (i + 7 > Npts) ? Npts - i : 7;
	    drawpoints(Pts, pt, pt+1);
	    drawcurve(&Pts[i], n);
	    setpoint(Pts, pt, mx, my);
	    drawcurve(&Pts[i], n);
	    drawpoints(Pts, pt, pt+1);
	}
	if (gg) {
	    char buf[32];
	    if (gg == GADGETUP)
		gg = 0;
	    if (gy + 1 >= 0 && gy + 1 != Step) {
		Step = gy + 1;
		sprintf(buf, "gran: %4ld/%ld", Step, ONE);
		clearall();
		drawcurve(Pts, Npts);
		drawpoints(Pts, 0, Npts);
		Move(Rp, Ux + 1, Uy + 16);
		Text(Rp, buf, strlen(buf));
	    }
	}
    }
    exiterr(1, NULL);
}

exiterr(n, str)
char *str;
{
    if (n) {
	if (str)
	    puts(str);
	if (Win) {
	    uninit_menu(Win);
	    CloseWindow(Win);
	}
	closelibs(-1);
	exit(1);
    }
}

setbounds(a, na)
register long *a;
{
    Ux = Win->BorderLeft;
    Uy = Win->BorderTop;
    Lx = Win->Width - Win->BorderRight;
    Ly = Win->Height- Win->BorderBottom + 1;
    clearall();
    drawcurve(a, na);
    drawpoints(a, 0, na);
}

setpoint(a, pt, x, y)
register short a[4][2];
{
    a[pt][0] = x;
    a[pt][1] = y;
}

getpoint(a, na, x, y)
register short a[][2];
{
    register short i, bi;
    register long r, br;

    for (i = bi = 0, br = 0x7FFFFFFF; i < na; ++i) {
	r = (x-a[i][0])*(x-a[i][0]) + (y-a[i][1])*(y-a[i][1])*3;
	if (r < br) {
	    bi = i;
	    br = r;
	}
    }
    return(bi);
}

clearall()
{
    SetAPen(Rp, 0);
    SetDrMd(Rp, JAM2);
    RectFill(Rp, Ux, Uy, Lx - 1, Ly - 1);
    SetAPen(Rp, 3);
    SetDrMd(Rp, COMPLEMENT);
}

#define S10(x)	 ((x) >> SHIFTS)
#define S20(x)	 ((x) >> (2*SHIFTS))

/*
 *  So I can use integer arithmatic, I am defining 512 as 1 (as far
 *  as the mathematics go), which means that I must divide any power
 *  multiplication by 512^(n-1).  E.G. .5^2 = .25 ... to make 256^2
 *  equal 128, I must divide by 512^1
 */

static short Array[ONE+4][2];	/* hold points to plot		*/

drawcurve(a, na)
register short a[][2];
{
    long  mr[4];
    register short n, i, t;
    long tt, ttt;
    short last;

    for (i = 1; i < na - 2; ++i) {
	for (t = n = last = 0; t <= ONE; t += Step) {
oncemore:
	    tt = t * t;
	    ttt= tt* t;
	    mr[0] = -S20(ttt/6) + S10(tt/2) - t/2 + ONE/6;
	    mr[1] =  S20(ttt/2) - S10(tt)   + ONE*2/3;
	    mr[2] = -S20(ttt/2) + S10(tt/2) + t/2 + ONE/6;
	    mr[3] =  S20(ttt/6);

	    Array[n][0] = (mr[0] * a[i-1][0] + mr[1] * a[i][0] + mr[2] * a[i+1][0] + mr[3] * a[i+2][0]) >> SHIFTS;
	    Array[n][1] = (mr[0] * a[i-1][1] + mr[1] * a[i][1] + mr[2] * a[i+1][1] + mr[3] * a[i+2][1]) >> SHIFTS;
	    if (++n == ONE + 4) {
		--n;
		puts("software error: OVERFLOW");
	    }
	}
	if (last == 0 && t > ONE) {
	    t = ONE;
	    last = 1;
	    goto oncemore;
	}
	Move(Rp, Array[0][0], Array[0][1]);
	PolyDraw(Rp, n, Array);
    }
}


drawpoints(a, is, ie)
register short a[][2];
{
    register short i;

    for (i = is; i < ie; ++i) {
	if (Display) {
	    char buf[32];
	    Move(Rp, a[i][0], a[i][1]);
	    Draw(Rp, a[i][0], a[i][1]);
	    Move(Rp, a[i][0] - 16, a[i][1] + 4);
	    sprintf(buf, "%ld", i);
	    Text(Rp, buf, strlen(buf));
	} else {
	    Move(Rp, a[i][0] - 2, a[i][1]);
	    Draw(Rp, a[i][0] + 2, a[i][1]);
	    Move(Rp, a[i][0], a[i][1] - 2);
	    Draw(Rp, a[i][0], a[i][1] + 2);
	}
    }
}

/*
 *  GADGET ROUTINES!	------------------------------------------------
 */


#define NG(nn)	&Gadgets[nn+1]
#define G_YGLOB 1
#define G_XGLOB 2

XPI Props[] = {
    { AUTOKNOB|FREEVERT , 0, 0, 0x1FFF, 0x1FFF }
};

IM Images[] = {
    { 0,0,2,1,1, NULL, 1, 0, NULL },
};

GADGET Gadgets[] = {
    {
	NULL, -15, 11, 15, -19, GADGIMAGE|GADGHCOMP|GRELRIGHT|GRELHEIGHT,
	GADGIMMEDIATE|RIGHTBORDER|RELVERIFY,PROPGADGET,
	(APTR)&Images[0],NULL,NULL,0,(APTR)&Props[0], G_YGLOB, 0
    },
};

GADGET *Gc;
long GUx, GUy;

init_gadgets(nw, ppo)
NW *nw;
XPI **ppo;
{
    nw->FirstGadget = &Gadgets[0];
    *ppo = &Props[0];
}

/*
 *  MENU ROUTINES! -----------------------------------------------------
 */

ITEXT Itext[] = {
    { 0,1,JAM2,0,0,NULL,(UBYTE *)"  Move",  NULL },
    { 0,1,JAM2,0,0,NULL,(UBYTE *)"  Add",   NULL },
    { 0,1,JAM2,0,0,NULL,(UBYTE *)"  Delete",NULL },
    { 0,1,JAM2,0,0,NULL,(UBYTE *)"  Cross", NULL },
    { 0,1,JAM2,0,0,NULL,(UBYTE *)"  Number",NULL }
};


MENUITEM Item0[] = {
    { &Item0[1], 0, 0, 8*11, 8, CHECKIT|ITEMENABLED|COMMSEQ|HIGHCOMP|ITEMTEXT,
      0xFE, (APTR)&Itext[0], NULL, 'M', NULL, NULL
    },
    {
      &Item0[2], 0, 8, 8*11, 8, CHECKIT|ITEMENABLED|COMMSEQ|HIGHCOMP|ITEMTEXT,
      0xFD, (APTR)&Itext[1], NULL, 'A', NULL, NULL
    },
    {
      NULL,	0,16, 8*11, 8, CHECKIT|ITEMENABLED|COMMSEQ|HIGHCOMP|ITEMTEXT,
      0xFB, (APTR)&Itext[2], NULL, 'D', NULL, NULL
    }
};

MENUITEM Item1[] = {
    { &Item1[1], 0, 0, 8*11, 8, CHECKIT|ITEMENABLED|COMMSEQ|HIGHCOMP|ITEMTEXT,
      0xFE, (APTR)&Itext[3], NULL, 'C', NULL, NULL
    },
    {
      NULL     , 0, 8, 8*11, 8, CHECKIT|ITEMENABLED|COMMSEQ|HIGHCOMP|ITEMTEXT,
      0xFD, (APTR)&Itext[4], NULL, 'N', NULL, NULL
    }
};


MENU Menu[] = {
    { &Menu[1], 0,   0,8*11, 8, MENUENABLED, "Control", &Item0[0] },
    { NULL    , 8*11,0,8*11, 8, MENUENABLED, "Display", &Item1[0] }
};

init_menu(win)
WIN *win;
{
    SetMenuStrip(win, &Menu[0]);
}

uninit_menu(win)
WIN *win;
{
    ClearMenuStrip(win);
}



