/*  :ts=8 bk=0
 *
 * ms.c:	Accompanys the MultiSprite article in AmigaWorld Tech
 *		Journal.  If it's not explained here, it's probably explained
 *		there.
 *
 *		This is a 1.3 program; 2.0 features are not illustrated or
 *		used.
 *
 *		Compile under Lattice 5.10 with the following command:
 *		lc -. -v -cq -rr -L ms.c
 *
 * Leo L. Schwab					9011.13
 */
#include <exec/types.h>
#include <graphics/sprite.h>
#include <graphics/gfxbase.h>
#include <intuition/intuition.h>
#include <string.h>
#include <stdlib.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/graphics.h>
#include <proto/intuition.h>

#define	SPRHIGH		20

/*
 * Forward function declarations.  (I hate ANSI.)
 */
LONG main (void);
void MyMoveSprite (struct ViewPort *, struct SimpleSprite *, LONG, LONG);
void bounce (short *, short *, short);
void openstuff (void);
void closestuff (void);
void die (char *);


/*
 * Sprite data.  This is the 'Wait' pointer image from Disney Animation
 * Studio.  (Well, I just happen to have it laying around...)
 */
static UWORD __chip	sprdat[] = { 
#define	SPRDAT1		sprdat
	0x0000, 0x0000,		/*  First set of control words.		*/
	0x7E0, 0x7E0,
	0x870, 0xF90,
	0x836, 0xFD6,
	0xFF3, 0xFF3,
	0x2, 0x2,
	0xFF6, 0xFF6,
	0x318C, 0x3E7C,
	0x4812, 0x77EE,
	0x4002, 0x7FFE,
	0xA005, 0xDFFB,
	0x8181, 0xFFFF,
	0xFFC3, 0xBFFD,
	0x8181, 0xFFFF,
	0xA045, 0xDFFB,
	0x4022, 0x7FFE,
	0x4812, 0x77EE,
	0x318C, 0x3E7C,
	0xFF0, 0xFF0,
	0x1818, 0x1818,
	0x300C, 0x300C,
#define	SPRDAT2		(&sprdat[21 * 2])
	0x0000, 0x0000,		/*  Next set of control words.		*/
	0x7E0, 0x7E0,
	0x870, 0xF90,
	0x836, 0xFD6,
	0xFF3, 0xFF3,
	0x2, 0x2,
	0xFF6, 0xFF6,
	0x318C, 0x3E7C,
	0x4812, 0x77EE,
	0x4002, 0x7FFE,
	0xA005, 0xDFFB,
	0x8181, 0xFFFF,
	0xFFC3, 0xBFFD,
	0x8181, 0xFFFF,
	0xA045, 0xDFFB,
	0x4022, 0x7FFE,
	0x4812, 0x77EE,
	0x318C, 0x3E7C,
	0xFF0, 0xFF0,
	0x1818, 0x1818,
	0x300C, 0x300C,
	0x0000, 0x0000		/*  End of all sprite data.		*/
};

/*
 * Preset SimpleSprite structures.
 */
struct SimpleSprite	s1 = {
	SPRDAT1,		/*  posctldata	*/
	SPRHIGH,		/*  height	*/
	0, 0, 0			/*  x, y, num	*/
};
struct SimpleSprite	s2 = {
	SPRDAT2,		/*  posctldata	*/
	SPRHIGH,		/*  height	*/
	0, 0, 0			/*  x, y, num	*/
};


/*
 * Intuition Window.
 */
struct NewWindow windef = {
	0, 50, 200, 10,
	-1, -1,
	CLOSEWINDOW,
	WINDOWCLOSE | WINDOWDEPTH | WINDOWDRAG | ACTIVATE | SIMPLE_REFRESH,
	NULL, NULL,
	(UBYTE *) "MultiSprite",
	NULL, NULL,
	160, 10, 160, 10,
	WBENCHSCREEN
};

struct Window		*win;
struct ViewPort		*svp;
short			wide, high;

struct GfxBase		*GfxBase;
struct IntuitionBase	*IntuitionBase;


LONG
main ()
{
	struct IntuiMessage	*msg;
	short			maxx, maxy, midy;
	short			x1, y1, x2, y2,
				dx1, dy1, dx2, dy2;

	openstuff ();

	/*
	 * Establish travelling limits of sprites.
	 */
	maxx = wide - 16 - 1;
	if (svp->Modes & HIRES)
		maxx -= 16;

	midy = high >> 1;
	maxy = midy - SPRHIGH;
	if (svp->Modes & LACE)
		maxy -= SPRHIGH;

	/*
	 * Initialize sprite coordinates and bounce factors.
	 */
	x1 = 0;  y1 = 0;
	x2 = 0;  y2 = 0;

	dx1 = 1;  dy1 = 1;
	dx2 = 3;  dy2 = 2;

	/*
	 * Ask the system to turn on our sprites.
	 */
	ChangeSprite (svp, &s1, SPRDAT1);

	while (1) {
		/*  Slow things down so we can see it.  */
		WaitTOF ();

		/*
		 * See if we should exit.
		 */
		if (msg = (struct IntuiMessage *) GetMsg (win->UserPort))
			/*  Yes, we should.  (CLOSEWINDOW)  */
			break;

		/*
		 * Bounce sprites around.
		 */
		bounce (&x1, &dx1, maxx);
		bounce (&x2, &dx2, maxx);
		bounce (&y1, &dy1, maxy);
		bounce (&y2, &dy2, maxy);

		/*
		 * Actually change on-screen locations.
		 */
		MyMoveSprite (svp, &s1, x1, y1);
		MyMoveSprite (svp, &s2, x2, y2 + midy);
	}

	ReplyMsg (msg);

	closestuff ();
	return (0);
}

/*
 * This function computes and writes a new set of position control words to
 * the sprite image data.  Note that, unlike MoveSprite(), this function does
 * not support attached sprites, though it does preserve the SPRITE_ATTACHED
 * bit.  You will have to move attached sprites on your own if you use this
 * function to do it.
 */
void
MyMoveSprite (vp, spr, x, y)
register struct ViewPort	*vp;
register struct SimpleSprite	*spr;
LONG				x, y;
{
	register UWORD	pos, ctrl;

	/*
	 * Update SimpleSprite structure's coordinates.
	 */
	spr->x = x;
	spr->y = y;

	if (vp) {
		/*
		 * Add ViewPort offset to supplied coordinates.
		 */
		x += vp->DxOffset;
		y += vp->DyOffset;

		/*
		 * Convert prevailing ViewPort pixel coordinates into lo-res
		 * non-lace pixel coordinates, since that's the form required
		 * by the sprite hardware.
		 */
		if (vp->Modes & HIRES)
			x >>= 1;
		if (vp->Modes & LACE)
			y >>= 1;
	}

	/*
	 * Add View offsets to coordinates.  (View offsets are always in
	 * lo-res non-lace pixel units.)
	 */
	x += GfxBase->ActiView->DxOffset;
	y += GfxBase->ActiView->DyOffset;

	/*
	 * Compute value to poke into sprite control data.  HSTART is
	 * limited to the effective range 0 - 511.
	 *
	 * Note:  Performance-conscious individuals may argue that all this
	 * should be done in assembly language.  They are right.
	 * However, I wanted this to be readable (as if C is readable...).
	 */

/*	            YSTART: bits 7-0        HSTART: bits 8-1		*/
	pos =		(y << 8)       +   ((x >> 1) & 0xFF);

/*		    YSTOP:  bits 7-0					*/
	ctrl = ((y + spr->height) << 8) +
/*		   Preserve SPRITE_ATTACHED bit.			*/
	       (spr->posctldata[1] & SPRITE_ATTACHED);

	/*  Set YSTART: bit 8  */
	if (y & 0x100)
		ctrl |= 4;

	/*  Set YSTOP:  bit 8  */
	if (y + spr->height & 0x100)
		ctrl |= 2;

	/*  Set HSTART: bit 0  */
	ctrl |= x & 1;

	/*
	 * Poke sprite data.  Do it as a ULONG to make it quick(er).
	 *
	 * NOTE:  This is done while the sprite data is being displayed.
	 * For the purposes of an example program, this is acceptable.
	 * However, poking this data on the fly can get you into trouble if
	 * your program happens to poke the data while the hardware is
	 * reading it.  The result can be one video field of trashed sprite
	 * imagery, or a missing sprite.
	 *
	 * To avoid this, you may wish to double-buffer your sprite lists.
	 */
	* (ULONG *) spr->posctldata = (pos << 16) + ctrl;
}

void
bounce (x, dx, maxx)
register short	*x, *dx, maxx;
{
	/*  Add offset.  */
	*x += *dx;

	/*  If outside limits, clip to limits and invert delta.  */
	if (*x < 0) {
		*x = 0;
		goto flip;		/*  Yeah, it's a goto.  So sue me. */
	} else if (*x > maxx) {
		*x = maxx;
flip:		*dx = -*dx;
	}
}


void
openstuff ()
{
	register short	i;
	struct Screen	sensor;

	/*
	 * Setup some basic stuff.
	 */
	s1.num = -1;	/*  No sprite allocated.  */

	if (!(IntuitionBase = OpenLibrary ("intuition.library", 0L)))
		die ("Can't open intuition.\n");

	if (!(GfxBase = (struct GfxBase *)
	      OpenLibrary ("graphics.library", 0L)))
		die ("Can't open graphics.\n");

	if (!(win = OpenWindow (&windef)))
		die ("Window painted shut.\n");
	svp = ViewPortAddress (win);

	/*
	 * Get sizing information about the Workbench screen.
	 * (This is doubtless done very differently under 2.0.)
	 */
	GetScreenData ((char *) &sensor, sizeof(sensor), WBENCHSCREEN, NULL);
	wide = sensor.Width;
	high = sensor.Height;

	/*
	 * Get sprite #2.
	 *
	 * Note:  I would have grabbed the next available sprite (using -1),
	 * but if I ended up with sprite #1 (highly likely), changing its
	 * colors would have also changed your Intuition pointer's colors.
	 * I decided this was undesireable.
	 */
	if (GetSprite (&s1, 2) < 0)
		die ("Couldn't get sprite.\n");
	s2.num = s1.num;	/*  For consistency.  */

	/*
	 * Set sprite colors.
	 */
	i = (s1.num << 1 & ~3) + 16;
	SetRGB4 (svp, ++i, 0, 0, 15);
	SetRGB4 (svp, ++i, 0, 15, 0);
	SetRGB4 (svp, ++i, 15, 0, 0);
}

void
closestuff ()
{
	if (s1.num >= 0)	FreeSprite (s1.num);
	if (win)		CloseWindow (win);
	if (GfxBase)		CloseLibrary (GfxBase);
	if (IntuitionBase)	CloseLibrary (IntuitionBase);
}

void
die (str)
char *str;
{
	register LONG	fh;

	if (fh = Output ())
		Write (fh, str, (long) strlen (str));
	closestuff ();
	exit (20);
}
