//////////////////////////////////////////////////////////////////////////////
//
//  This file is part of the Atari Machine Specific Library,
//  and is Copyright 1992 by Warwick W. Allison.
//
//  You are free to copy and modify these sources, provided you acknoledge
//  the origin by retaining this notice, and adhere to the conditions
//  described in the file COPYING.
//
//////////////////////////////////////////////////////////////////////////////

//  I/O return values need work.  Sorry - hacky 'game' mentality.

#ifndef __OPTIMIZE__
#error "Due to register restrictions, this file must be optimized (-O)"
#endif


#include "sprite.h"
#include "doublebuffer.h"
#include <alloc.h>

Incarnation::Incarnation(short h, int b)
	: height(h), Backing(b)
{ }

void Incarnation::SetHotSpot(short x, short y)
{
	HotX=x;
	HotY=y;
}

void Sprite::Draw()
{
	long* Back=BackingStore[Pages->Pulse];
	*Back++=(long)Inca[shape];
	Inca[shape]->Draw(x >> Shift,y >> Shift,Back);
}

int Sprite::TouchDraw()
{
	long* Back=BackingStore[Pages->Pulse];
	*Back++=(long)Inca[shape];
	return Inca[shape]->TouchDraw(x >> Shift,y >> Shift,Back);
}

void Sprite::Wipe()
{
	long *Back=BackingStore[Pages->Pulse];
	Incarnation* OldInca=(Incarnation*)*Back;
	if (OldInca) {
		*Back++=0;
		OldInca->Wipe(Back);
	}
}

short LeftBits(unsigned short x)
{
	for (int i=0; x; x<<=1) i++;
	return i;
}

short LongLeftBits(unsigned long x)
{
	for (int i=0; x; x<<=1) i++;
	return i;
}

Sprite::Sprite(Sprite& Copy)
{
	*this=Copy;

	BackingStore[0]=new long[BackingSize];
	BackingStore[1]=new long[BackingSize];
	BackingStore[0][0]=0;
	BackingStore[1][0]=0;

	ExternalInca=TRUE;
}

Sprite::Sprite(Incarnation **List,int Count)
{
	x=50;
	y=50;
	shape=0;

	BackingSize=2; // Nothing really
	for (int i=0; i<Count; i++) {
		if (List[i]->BackingRequired() > BackingSize)
			BackingSize=List[i]->BackingRequired();
	}
	BackingSize++; // For OldInca
	BackingStore[0]=new long[BackingSize];
	BackingStore[1]=new long[BackingSize];
	BackingStore[0][0]=0;
	BackingStore[1][0]=0;

	OverlayMask=0;

	MaxInca=Count;
	Inca=List;
	ExternalInca=TRUE;
}

Sprite::Sprite(Incarnation *OnlyOne)
{
	x=50;
	y=50;
	shape=0;

	BackingSize=OnlyOne->BackingRequired()+1;
	BackingStore[0]=new long[BackingSize];
	BackingStore[1]=new long[BackingSize];
	BackingStore[0][0]=0;
	BackingStore[1][0]=0;

	OverlayMask=0;

	MaxInca=1;
	Inca=new Incarnation*[1];

	Inca[0]=OnlyOne;
	ExternalInca=FALSE;
}

Sprite::Sprite(FILE *fp) :
	Inca(0)
{
	if (!fget(fp)) {
		// ?
	}
}

Sprite::Sprite(const char *filename)
{
	FILE* fp=fopen(filename,"rb");
	fget(fp);
	if (!fclose(fp)) {
		// ?
	}
}

Sprite::Sprite(short maxinca)
{
	x=50;
	y=50;
	shape=0;

	BackingSize=2; // Nothing really
	BackingStore[0]=new long[BackingSize];
	BackingStore[1]=new long[BackingSize];
	BackingStore[0][0]=0;
	BackingStore[1][0]=0;

	OverlayMask=0;

	MaxInca=maxinca;
	Inca=new Incarnation*[MaxInca];

	for (int i=0; i<MaxInca; i++) Inca[i]=0;
	ExternalInca=FALSE;
}

Sprite::~Sprite()
{
	delete BackingStore[0];
	delete BackingStore[1];
	if (!ExternalInca) {
		for (int i=0; i<MaxInca; i++) if (Inca[i]) delete Inca[i];
		delete Inca;
	}
}

void	Sprite::SetImage(int i, Incarnation* In)
{
	if (BackingSize < In->BackingRequired()+1) {
		BackingSize=In->BackingRequired()+1;
		delete BackingStore[0];
		delete BackingStore[1];
		BackingStore[0]=new long[BackingSize];
		BackingStore[1]=new long[BackingSize];
		BackingStore[0][0]=0;
		BackingStore[1][0]=0;
	}
	Inca[i]=In;
}

#define READ(x) fread(&x,sizeof(x),1,fp)
#define WRITE(x) fwrite(&x,sizeof(x),1,fp)

Incarnation* IncarnationReader(FILE *fp)
{
	short Tag;

	fread(&Tag,sizeof(Tag),1,fp);

	switch (Tag) {
	 case 0:
		return new MonochromeIncarnation(fp);
	break; case 1:
		return new ColourIncarnation(fp);
	break; case 2:
		return new WideMonochromeIncarnation(fp);
	break; case 3:
		return new WideColourIncarnation(fp);
	break; case 4:
		return new PreshiftedMonochromeIncarnation(fp);
	break; case 5:
		return new PreshiftedColourIncarnation(fp);
	break; default:
		return 0;
	}
}

int	Sprite::fget(FILE *fp)
{
	fread(&x,sizeof(x),1,fp);
	fread(&y,sizeof(y),1,fp);
	fread(&shape,sizeof(shape),1,fp);
	fread(&Shift,sizeof(Shift),1,fp);

	if (Inca && !ExternalInca) {
		for (int i=0; i<MaxInca; i++) if (Inca[i]) delete Inca[i];
		delete Inca;
	}

	fread(&MaxInca,sizeof(MaxInca),1,fp);

	Inca=new Incarnation*[MaxInca];

	int i;

	for (i=0; i<MaxInca; i++) {
		short yes;
		READ(yes);
		if (yes) {
			Inca[i]=IncarnationReader(fp);
		} else {
			Inca[i]=0;
		}
	}

	BackingSize=2; // Nothing really
	for (i=0; i<MaxInca; i++) {
		if (Inca[i] && Inca[i]->BackingRequired() > BackingSize)
			BackingSize=Inca[i]->BackingRequired();
	}
	BackingSize++; // For OldInca
	BackingStore[0]=new long[BackingSize];
	BackingStore[1]=new long[BackingSize];
	BackingStore[0][0]=0;
	BackingStore[1][0]=0;

	ExternalInca=FALSE;

	return 1;
}

int	Sprite::fput(FILE *fp)
{
	fwrite(&x,sizeof(x),1,fp);
	fwrite(&y,sizeof(y),1,fp);
	fwrite(&shape,sizeof(shape),1,fp);
	fwrite(&Shift,sizeof(Shift),1,fp);
	fwrite(&MaxInca,sizeof(MaxInca),1,fp);

	for (int i=0; i<MaxInca; i++) {
		short yes=(Inca[i]!=0);
		fwrite(&yes,sizeof(yes),1,fp);
		if (yes) Inca[i]->fput(fp);
	}

	return 1;
}

int	Sprite::Load(const char *filename)
{
	FILE* fp=fopen(filename,"rb");
	if (!fp) return 0;
	fget(fp);
	return fclose(fp);
}

int	Sprite::Save(const char *filename)
{
	FILE* fp=fopen(filename,"wb");
	if (!fp) return 0;
	fput(fp);
	return fclose(fp);
}

MonochromeIncarnation::MonochromeIncarnation(int h) :
	Incarnation(h,h+2),
	Data(new unsigned short[h]),
	Mask(new unsigned short[h])
{
}

MonochromeIncarnation::~MonochromeIncarnation()
{
	delete Data;
	delete Mask;
}

int	Incarnation::fput(FILE *fp)
{
	fwrite(&width,sizeof(width),1,fp);
	fwrite(&height,sizeof(height),1,fp);
	fwrite(&HotX,sizeof(HotX),1,fp);
	return (fwrite(&HotY,sizeof(HotY),1,fp)==1);
}

Incarnation::Incarnation(FILE *fp)
{
	READ(width);
	READ(height);
	READ(HotX);
	READ(HotY);
}

int	MonochromeIncarnation::fput(FILE *fp)
{
	short tag=0;
	fwrite(&tag,sizeof(tag),1,fp);
	Incarnation::fput(fp);
	fwrite(Data,sizeof(*Data),height,fp);
	return (fwrite(Mask,sizeof(*Mask),height,fp)==height);
}

MonochromeIncarnation::MonochromeIncarnation(FILE *fp) :
	Incarnation(fp),
	Data(new unsigned short[height]),
	Mask(new unsigned short[height])
{
	Backing=height+2;
	fread(Data,sizeof(*Data),height,fp);
	fread(Mask,sizeof(*Mask),height,fp);
}


int	MonochromeIncarnation::TouchDraw(short x, short y, long *Store)
{ return 0; }

void	MonochromeIncarnation::Draw(short x, short y, long *Store)
{
	x-=HotX;
	y-=HotY;
	short LineSpacing=BytesPerLine[Pages->Current().Rez()];
	long *At = (long*)(Pages->Location() + y*LineSpacing + ((x>>4)<<1));
	unsigned short shift = 16-(x&15);
#ifdef NOASM
	unsigned short *D=Data;
	unsigned short *M=Mask;

	*(Store++)=(long)At;
	*(Store++)=(long)height-1;
	for (int h=0; h<height; h++) {
		*(Store++)=*At;
		*At=(*At & ~((long)(*(M++)) << shift)) | ((long)(*(D++)) << shift);
		At=(long*)((long)At+LineSpacing);
	}
#else
	asm("
			subl	#1,%6
			movel	%0,%5@+
			movel	%6,%5@+
		0:	movel	%0@,d0
			movel	d0,%5@+
			clrl	d1
			movew	%1@+,d1
			asll	%3,d1
			notl	d1
			andl	d1,d0
			clrl	d1
			movew	%2@+,d1
			asll	%3,d1
			orl	d1,d0
			movel	d0,%0@
			addl	%4,%0
			dbra	%6,0b
		1:
	"	: 
		: "a" (At),
		  "a" (Mask),
		  "a" (Data),
		  "d" (shift),
		  "d" ((long)LineSpacing),
		  "a" (Store),
		  "d" (height)
		
		: "d0", "d1");
#endif
}

void	SimpleMonoWipe(long *Store)
{
	short LineSpacing=BytesPerLine[Pages->Current().Rez()];
#ifdef NOASM
	if (*Store) {
		long *At = (long*)*Store;
		*(Store++) = 0;

		for (int h=1+*(Store++); h; h--) {
			*At=*(Store++);
			At=(long*)((long)At+LineSpacing);
		}
	}
#else
	asm("
	movel	%0@,d0		| Since moves to address regs don't change Z flag
	beq	1f
	movel	d0,a0
	clrl	%0@+
	movel	%0@+,d0
0:
	movel	%0@+,a0@
	addl	%1,a0		| next line on screen
	dbra	d0,0b
1:
" :
  :	"a" (Store),
	"d" ((long)LineSpacing)

  :	"d0", "a0"
);
#endif
}

void	MonochromeIncarnation::Wipe(long *Store)
{
	SimpleMonoWipe(Store);
}

void	MonochromeIncarnation::GetImage(Screen& S, int x, int y)
{
	short LineSpacing=BytesPerLine[S.Rez()];
	unsigned short *At=(unsigned short*)(S.Location()+y*LineSpacing+x/16*2);
	unsigned short *D,*M;
	unsigned short or=0;

	D=Data;
	M=Mask;
	for (int h=0; h<height; h++) {
		*D++=At[0];
		*M++=At[1];
		or|=At[1];
		At=(unsigned short*)((long)At+LineSpacing);
	}

	width=LeftBits(or);
}


WideMonochromeIncarnation::WideMonochromeIncarnation(int h) :
	Incarnation(h,(h+1)*3/2+2),
	Data(new unsigned long[h]),
	Mask(new unsigned long[h])
{ }

WideMonochromeIncarnation::~WideMonochromeIncarnation()
{
	delete Data;
	delete Mask;
}

int	WideMonochromeIncarnation::fput(FILE *fp)
{
	short tag=2;
	fwrite(&tag,sizeof(tag),1,fp);
	Incarnation::fput(fp);
	fwrite(Data,sizeof(*Data),height,fp);
	return fwrite(Mask,sizeof(*Mask),height,fp)==height;
}

WideMonochromeIncarnation::WideMonochromeIncarnation(FILE *fp) :
	Incarnation(fp),
	Data(new unsigned long[height]),
	Mask(new unsigned long[height])
{
	Backing=(height+1)*3/2+2;
	fread(Data,sizeof(*Data),height,fp);
	fread(Mask,sizeof(*Mask),height,fp);
}



int	WideMonochromeIncarnation::TouchDraw(short x, short y, long *Store)
{ return 0; }

void	WideMonochromeIncarnation::Draw(short x, short y, long *Store)
{
	x-=HotX;
	y-=HotY;
	short LineSpacing=BytesPerLine[Pages->Current().Rez()];
	long *At = (long*)(Pages->Location() + y*LineSpacing + x/16*2);
#ifdef NOASM
	error("This is too hard to do without assembler");
#else
	short rshift=x&15;
	short lshift=16-rshift;

	asm("
	movel	%5,%2@+		| Save output address
	subl	#1,%6
	movel	%6,%2@+		| Save Height-1
0:
|	Save scanline:
	movel	%5@,%2@+
	movew	%5@(4),%2@+

|	Get mask
	movel	%1@+,d3
	movew	d3,d4
	lsrl	%4,d3
	aslw	%3,d4
	notl	d3
	notw	d4

|	Draw scanline:
	movel	%0@+,d1
	movew	d1,d2
	lsrl	%4,d1
	aslw	%3,d2

	movew	%5@(4),d0	| First the right end word
	andw	d4,d0
	orw	d2,d0
	movew	d0,%5@(4)
	movel	%5@,d0		| Then the left end long
	andl	d3,d0
	orl	d1,d0
	movel	d0,%5@
	
	addl	%7,%5		| next line on screen

	dbra	%6,0b

" :
  :	"a" (Data),
	"a" (Mask),
	"a" (Store),
	"d" (lshift),
	"d" (rshift),
	"a" (At),
	"d" ((long)height),
	"a" ((long)LineSpacing)

  :	"d0", "d1", "d2", "d3", "d4"
);
#endif
}

void	WideMonochromeIncarnation::Wipe(long *Store)
{
	short LineSpacing=BytesPerLine[Pages->Current().Rez()];
#ifdef NOASM
	error("Too hairy mixing long and short without ASM");
#else
	asm("
	movel	%0@,d0		| Since moves to address regs don't change Z flag
	beq	1f
	movel	d0,a0
	clrl	%0@+
	movel	%0@+,d0
	subl	#4,%1
0:
	movel	%0@+,a0@+
	movew	%0@+,a0@
	addl	%1,a0		| next line on screen
	dbra	d0,0b
1:
" :
  :	"a" (Store),
	"d" ((long)LineSpacing)

  :	"d0", "a0"
);
#endif
}

void	WideMonochromeIncarnation::GetImage(Screen& S, int x, int y)
{
	short LineSpacing=BytesPerLine[S.Rez()];
	unsigned long *At=(unsigned long*)(S.Location()+y*LineSpacing+x/16*2);
	unsigned long or=0,*D,*M;

	D=Data;
	M=Mask;
	for (int h=0; h<height; h++) {
		*D=*At;
		*M=At[1];
		or|=*M++;
		D++;
		At=(unsigned long*)((long)At+LineSpacing);
	}
	width=LongLeftBits(or);
}



PreshiftedMonochromeIncarnation::PreshiftedMonochromeIncarnation(int h)
	: Incarnation(h,h+2)
{
	int s;

	for (s=0; s<16; s++) Data[s]=new unsigned long[height];
	for (s=0; s<16; s++) Mask[s]=new unsigned long[height];
}

PreshiftedMonochromeIncarnation::~PreshiftedMonochromeIncarnation()
{
	int s;

	for (s=0; s<16; s++) delete Data[s];
	for (s=0; s<16; s++) delete Mask[s];
}

int	PreshiftedMonochromeIncarnation::fput(FILE *fp)
{
	short tag=4;
	fwrite(&tag,sizeof(tag),1,fp);
	Incarnation::fput(fp);
	fwrite(Data[0],sizeof(*Data[0]),height,fp);
	return fwrite(Mask[0],sizeof(*Mask[0]),height,fp)==height;
}

PreshiftedMonochromeIncarnation::PreshiftedMonochromeIncarnation(FILE *fp) :
	Incarnation(fp)
{
	int s;

	for (s=0; s<16; s++) Data[s]=new unsigned long[height];
	for (s=0; s<16; s++) Mask[s]=new unsigned long[height];

	Backing=height+2;
	fread(Data[0],sizeof(*Data[0]),height,fp);
	fread(Mask[0],sizeof(*Mask[0]),height,fp);

	// **** BELOW COPIED FROM GetImage - should be private member.
	unsigned long *D,*M,*SD,*SM;

	for (s=1; s<16; s++) {
		D=Data[0];
		M=Mask[0];

		SD=Data[s];
		SM=Mask[s];

		for (int h=0; h<height; h++) {
			*SD++=*D++>>s;
			*SM++=~(*M++>>s);
		}
	}

	M=Mask[0];
	for (int h=0; h<height; h++) {
		M[h]=~M[h];
	}
	// **** ABOVE COPIED FROM GetImage - should be private member.
}


int	PreshiftedMonochromeIncarnation::TouchDraw(short x, short y, long *Store)
{ return 0; }

void	PreshiftedMonochromeIncarnation::Draw(short x, short y, long *Store)
{
	x-=HotX;
	y-=HotY;
	unsigned short shift = x&15;
	short LineSpacing=BytesPerLine[Pages->Current().Rez()];
	long *At = (long*)(Pages->Location() + y*LineSpacing+((x>>4)<<1));
#ifdef NOASM
	unsigned long *D=Data[shift];
	unsigned long *M=Mask[shift];

	unsigned long m;

	*(Store++)=(long)At;
	*(Store++)=(long)height-1;
	for (int h=0; h<height; h++) {
		m=*(M++);

		*(Store++)=*At;

		*At=*At & m | *(D++);

		At=(long*)((long)At+LineSpacing);
	}
#else
	asm("
	movel	%2,%3@+		| Save output address
	subl	#1,%4
	movel	%4,%3@+		| Save Height-1
0:
|	Get mask
	movel	%0@+,d1

	movel	%1@+,d0
	movel	%2@,d4
	movel	d4,%3@+
	andl	d1,d4
	orl	d0,d4
	movel	d4,%2@

	addl	%5,%2	| next line on screen

	dbra	%4,0b
" :
  :	"a" (Mask[shift]),
	"a" (Data[shift]),
	"a" (At),
	"a" (Store),
	"d" (height),
	"d" ((long)LineSpacing)
  :	"d0", "d1", "d4"
);
#endif
}

void	PreshiftedMonochromeIncarnation::Wipe(long *Store)
{
	SimpleMonoWipe(Store);
}


void	PreshiftedMonochromeIncarnation::GetImage(Screen& S, int x, int y)
{
	short LineSpacing=BytesPerLine[S.Rez()];
	unsigned short *At=(unsigned short*)(S.Location()+y*LineSpacing+x/16*2);
	unsigned long *D,*M,*SD,*SM;
	unsigned short or=0;
	int h;

	D=Data[0];
	M=Mask[0];
	for (h=0; h<height; h++) {
		*D++=At[0] << 16;
		*M++=At[1] << 16;
		or|=At[1];
		At=(unsigned short*)((long)At+LineSpacing);
	}
	width=LeftBits(or);

	for (int s=1; s<16; s++) {
		D=Data[0];
		M=Mask[0];

		SD=Data[s];
		SM=Mask[s];

		for (h=0; h<height; h++) {
			*SD++=*D++>>s;

			*SM++=~(*M++>>s);
		}
	}

	M=Mask[0];
	for (h=0; h<height; h++) {
		M[h]=~M[h];
	}
}


WideColourIncarnation::WideColourIncarnation(int h) :
	Incarnation(h,h*6+2),
	Data(new unsigned long[h*4]),
	Mask(new unsigned long[h])
{ }

WideColourIncarnation::~WideColourIncarnation()
{
	delete Data;
	delete Mask;
}

int	WideColourIncarnation::fput(FILE *fp)
{
	short tag=3;
	fwrite(&tag,sizeof(tag),1,fp);
	Incarnation::fput(fp);
	fwrite(Data,sizeof(*Data),height*6,fp);
	return fwrite(Mask,sizeof(*Mask),height,fp)==height;
}

WideColourIncarnation::WideColourIncarnation(FILE *fp) :
	Incarnation(fp),
	Data(new unsigned long[height*6]),
	Mask(new unsigned long[height])
{
	Backing=height*6+2;
	fread(Data,sizeof(*Data),height*6,fp);
	fread(Mask,sizeof(*Mask),height,fp);
}


int	WideColourIncarnation::TouchDraw(short x, short y, long *Store)
{ return 0; }

void	WideColourIncarnation::Draw(short x, short y, long *Store)
{
	x-=HotX;
	y-=HotY;
	short LineSpacing=BytesPerLine[Pages->Current().Rez()];
	long *At = (long*)(Pages->Location() + y*LineSpacing+((x>>4)<<3));
#ifdef NOASM
	error("This is too hard to do without assembler");
#else
	short rshift=x&15;
	short lshift=16-rshift;

	asm("
	movel	%5,%2@+		| Save output address
	subl	#1,%6
	movel	%6,%2@+		| Save Height-1
0:
|	Save scanline:
	movel	%5@,%2@+
	movel	%5@(4),%2@+
	movel	%5@(8),%2@+
	movel	%5@(12),%2@+
	movel	%5@(16),%2@+
	movel	%5@(20),%2@+

|	Get mask
	movel	%1@+,d3
	movew	d3,d4
	lsrl	%4,d3
	aslw	%3,d4
	notl	d3
	notw	d4

|	Draw scanline:
	movel	%0@+,d1
	movew	d1,d2
	lsrl	%4,d1
	aslw	%3,d2

	movew	%5@(16),d0	| First the right end word
	andw	d4,d0
	orw	d2,d0
	movew	d0,%5@(16)
	movew	%5@,d0		| Then the left end long
	swap	d0
	movew	%5@(8),d0
	andl	d3,d0
	orl	d1,d0
	movew	d0,%5@(8)
	swap	d0
	movew	d0,%5@+
	
|	Draw scanline:
	movel	%0@+,d1
	movew	d1,d2
	lsrl	%4,d1
	aslw	%3,d2

	movew	%5@(16),d0	| First the right end word
	andw	d4,d0
	orw	d2,d0
	movew	d0,%5@(16)
	movew	%5@,d0		| Then the left end long
	swap	d0
	movew	%5@(8),d0
	andl	d3,d0
	orl	d1,d0
	movew	d0,%5@(8)
	swap	d0
	movew	d0,%5@+
	
|	Draw scanline:
	movel	%0@+,d1
	movew	d1,d2
	lsrl	%4,d1
	aslw	%3,d2

	movew	%5@(16),d0	| First the right end word
	andw	d4,d0
	orw	d2,d0
	movew	d0,%5@(16)
	movew	%5@,d0		| Then the left end long
	swap	d0
	movew	%5@(8),d0
	andl	d3,d0
	orl	d1,d0
	movew	d0,%5@(8)
	swap	d0
	movew	d0,%5@+
	
|	Draw scanline:
	movel	%0@+,d1
	movew	d1,d2
	lsrl	%4,d1
	aslw	%3,d2

	movew	%5@(16),d0	| First the right end word
	andw	d4,d0
	orw	d2,d0
	movew	d0,%5@(16)
	movew	%5@,d0		| Then the left end long
	swap	d0
	movew	%5@(8),d0
	andl	d3,d0
	orl	d1,d0
	movew	d0,%5@(8)
	swap	d0
	movew	d0,%5@+
	
	addl	%7,%5		| next line on screen
	subl	#8,%5

	dbra	%6,0b

" :
  :	"a" (Data),
	"a" (Mask),
	"a" (Store),
	"d" (lshift),
	"d" (rshift),
	"a" (At),
	"d" ((long)height),
	"a" ((long)LineSpacing)

  :	"d0", "d1", "d2", "d3", "d4"
);
#endif
}

void	WideColourIncarnation::Wipe(long *Store)
{
	short LineSpacing=BytesPerLine[Pages->Current().Rez()];
#ifdef NOASM
	if (*Store) {
		long *At = (long*) *Store;
		*(Store++) = 0;

		for (int h=1+*(Store++); h; h--) {
			*At=*(Store++);
			At[1]=*(Store++);
			At[2]=*(Store++);
			At[3]=*(Store++);
			At[4]=*(Store++);
			At[5]=*(Store++);
			At=(long*)((long)At+LineSpacing);
		}
	}
#else
	asm("
	movel	%0@,d0		| Since moves to address regs don't change Z flag
	beq	1f
	movel	d0,a0
	clrl	%0@+
	movel	%0@+,d0
	subl	#20,%1
0:
	movel	%0@+,a0@+
	movel	%0@+,a0@+
	movel	%0@+,a0@+
	movel	%0@+,a0@+
	movel	%0@+,a0@+
	movel	%0@+,a0@
	addl	%1,a0		| next line on screen
	dbra	d0,0b
1:
" :
  :	"a" (Store),
	"d" ((long)LineSpacing)

  :	"d0", "a0"
);
#endif
}

void	WideColourIncarnation::GetImage(Screen& S, int x, int y)
{
	short LineSpacing=BytesPerLine[S.Rez()];
	unsigned short *At=(unsigned short*)(S.Location()+y*LineSpacing+x/16*8);
	unsigned long or=0,*D,*M;

	D=Data;
	M=Mask;
	for (int h=0; h<height; h++) {
		D[0]=At[0]<<16|At[4];
		D[1]=At[1]<<16|At[5];
		D[2]=At[2]<<16|At[6];
		D[3]=At[3]<<16|At[7];
		*M=D[0]|D[1]|D[2]|D[3];
		or|=*M++;
		D+=4;
		At=(unsigned short*)((long)At+LineSpacing);
	}
	width=LongLeftBits(or);
}



ColourIncarnation::ColourIncarnation(int h) :
	Incarnation(h,h*4+2),
	Data(new unsigned short[h*4]),
	Mask(new unsigned short[h])
{ }

ColourIncarnation::~ColourIncarnation()
{
	delete Data;
	delete Mask;
}

int	ColourIncarnation::fput(FILE *fp)
{
	short tag=1;
	fwrite(&tag,sizeof(tag),1,fp);
	Incarnation::fput(fp);
	fwrite(Data,sizeof(*Data),height*4,fp);
	return fwrite(Mask,sizeof(*Mask),height,fp)==height;
}

ColourIncarnation::ColourIncarnation(FILE *fp) :
	Incarnation(fp),
	Data(new unsigned short[height*4]),
	Mask(new unsigned short[height])
{
	Backing=height*4+2;
	fread(Data,sizeof(*Data),height*4,fp);
	fread(Mask,sizeof(*Mask),height,fp);
}


int	ColourIncarnation::TouchDraw(short x, short y, long *Store)
{ return 0; }

void	ColourIncarnation::Draw(short x, short y, long *Store)
{
	x-=HotX;
	y-=HotY;
	unsigned short shift = 16-(x&15);
	short LineSpacing=BytesPerLine[Pages->Current().Rez()];
	long *At = (long*)(Pages->Location() + y*LineSpacing+((x>>4)<<3));
#ifdef NOASM
	unsigned short *D=Data;
	unsigned short *M=Mask;

	unsigned long m1,m2,d1,d2,t1,t2,t3,t4;

	*(Store++)=(long)At;
	*(Store++)=(long)height-1;
	for (int h=0; h<height; h++) {
		t1=~(((long)*(M++)) << shift);
		m1=(t1 & 0xffff0000) | ((t1 & 0xffff0000) >> 16);
		m2=((t1 & 0x0000ffff) << 16) | (t1 & 0x0000ffff);

		*(Store++)=*At;
		*(Store++)=At[1];
		*(Store++)=At[2];
		*(Store++)=At[3];

		t1=((long)*(D++)) << shift;
		t2=((long)*(D++)) << shift;
		t3=((long)*(D++)) << shift;
		t4=((long)*(D++)) << shift;
		d1=(t1 & 0xffff0000) | ((t2 & 0xffff0000) >> 16);
		d2=((t1 & 0x0000ffff) << 16) | (t2 & 0x0000ffff);
		At[0]=(At[0] & m1) | d1;
		At[2]=(At[2] & m2) | d2;
		d1=(t3 & 0xffff0000) | ((t4 & 0xffff0000) >> 16);
		d2=((t3 & 0x0000ffff) << 16) | (t4 & 0x0000ffff);
		At[1]=(At[1] & m1) | d1;
		At[3]=(At[3] & m2) | d2;

		At=(long*)((long)At+LineSpacing);
	}
#else
	asm("
	movel	%4,%2@+		| Save output address
	subl	#1,%5
	movel	%5,%2@+		| Save Height-1
0:
|	Save scanline:
	movel	%4@,%2@+
	movel	%4@(4),%2@+
	movel	%4@(8),%2@+
	movel	%4@(12),%2@+

|	Get mask
	clrl	d1
	movew	%1@+,d1
	asll	%3,d1
	notl	d1

|	Draw scanline:  Word 1
	clrl	d0
	movew	%0@+,d0
	asll	%3,d0
	movew	%4@,d4
	swap	d4
	movew	%4@(8),d4
	andl	d1,d4
	orl	d0,d4
	movew	d4,%4@(8)
	swap	d4
	movew	d4,%4@+

|	Draw scanline:  Word 2
	clrl	d0
	movew	%0@+,d0
	asll	%3,d0
	movew	%4@,d4
	swap	d4
	movew	%4@(8),d4
	andl	d1,d4
	orl	d0,d4
	movew	d4,%4@(8)
	swap	d4
	movew	d4,%4@+

|	Draw scanline:  Word 3
	clrl	d0
	movew	%0@+,d0
	asll	%3,d0
	movew	%4@,d4
	swap	d4
	movew	%4@(8),d4
	andl	d1,d4
	orl	d0,d4
	movew	d4,%4@(8)
	swap	d4
	movew	d4,%4@+

|	Draw scanline:  Word 4
	clrl	d0
	movew	%0@+,d0
	asll	%3,d0
	movew	%4@,d4
	swap	d4
	movew	%4@(8),d4
	andl	d1,d4
	orl	d0,d4
	movew	d4,%4@(8)
	swap	d4
	movew	d4,%4@+

	addl	%6,%4		| next line on screen
	subl	#8,%4

	dbra	%5,0b

" :
  :	"a" (Data),
	"a" (Mask),
	"a" (Store),
	"d" (shift),
	"a" (At),
	"d" ((long)height),
	"d" ((long)LineSpacing)

  :	"d0", "d1", "d4"
);
#endif
}

void	SimpleWipe(long *Store)
{
	short LineSpacing=BytesPerLine[Pages->Current().Rez()];
#ifdef NOASM
	if (*Store) {
		long *At = (long*) *Store;
		*(Store++) = 0;

		for (int h=1+*(Store++); h; h--) {
			*At=*(Store++);
			At[1]=*(Store++);
			At[2]=*(Store++);
			At[3]=*(Store++);
			At=(long*)((long)At+LineSpacing);
		}
	}
#else
	asm("
	movel	%0@,d0		| Since moves to address regs don't change Z flag
	beq	1f
	movel	d0,a0
	clrl	%0@+
	movel	%0@+,d0
	subl	#12,%1
0:
	movel	%0@+,a0@+
	movel	%0@+,a0@+
	movel	%0@+,a0@+
	movel	%0@+,a0@
	addl	%1,a0		| next line on screen
	dbra	d0,0b
1:
" :
  :	"a" (Store),
	"d" ((long)LineSpacing)

  :	"d0", "a0"
);
#endif
}

void	ColourIncarnation::Wipe(long *Store)
{
	SimpleWipe(Store);
}

void	ColourIncarnation::GetImage(Screen& S, int x, int y)
{
	short LineSpacing=BytesPerLine[S.Rez()];
	unsigned short *At=(unsigned short*)(S.Location()+y*LineSpacing+x/16*8);
	unsigned short or=0,*D,*M;

	D=Data;
	M=Mask;
	for (int h=0; h<height; h++) {
		*D++=At[0];
		*D++=At[1];
		*D++=At[2];
		*D++=At[3];
		*M=At[0]|At[1]|At[2]|At[3];
		or|=*M++;
		At=(unsigned short*)((long)At+LineSpacing);
	}
	width=LeftBits(or);
}



PreshiftedColourIncarnation::PreshiftedColourIncarnation(int h)
	: Incarnation(h,h*4+2)
{
	int s;

	for (s=0; s<16; s++) Data[s]=new unsigned short[height*4*2];
	for (s=0; s<16; s++) Mask[s]=new unsigned short[height*2];
}

PreshiftedColourIncarnation::~PreshiftedColourIncarnation()
{
	int s;

	for (s=0; s<16; s++) delete Data[s];
	for (s=0; s<16; s++) delete Mask[s];
}

int	PreshiftedColourIncarnation::fput(FILE *fp)
{
	short tag=5;
	fwrite(&tag,sizeof(tag),1,fp);
	Incarnation::fput(fp);
	fwrite(Data[0],sizeof(*Data[0]),height*4*2,fp);
	return fwrite(Mask[0],sizeof(*Mask[0]),height*2,fp)==height*2;
}

PreshiftedColourIncarnation::PreshiftedColourIncarnation(FILE *fp) :
	Incarnation(fp)
{
	int s;

	for (s=0; s<16; s++) Data[s]=new unsigned short[height*4*2];
	for (s=0; s<16; s++) Mask[s]=new unsigned short[height*2];

	Backing=height*4+2;

	fread(Data[0],sizeof(*Data[0]),height*4*2,fp);
	fread(Mask[0],sizeof(*Mask[0]),height*2,fp);

	unsigned short *D,*M,*SD,*SM;
	int h;

	// Invert mask (easier to shift)
	M=Mask[0];
	for (h=0; h<height; h++) {
		M[h*2]=~M[h*2];
		M[h*2+1]=~0;
	}

	// BELOW COPIED FROM GetImage
	unsigned long t;

	for (s=1; s<16; s++) {
		D=Data[0];
		M=Mask[0];

		SD=Data[s];
		SM=Mask[s];

		for (int h=0; h<height; h++) {
			for (int bp=0; bp<4; bp++) {
				t=(long)D[bp] << (16-s);
				SD[bp]=t>>16;
				SD[bp+4]=t&0xffff;
			}
			SD+=8;
			D+=8;

			t=((unsigned long)*M++ << (16-s));
			M++;
			*SM++=~(t>>16);
			*SM++=~(t&0xffff);
		}
	}

	M=Mask[0];
	for (h=0; h<height; h++) {
		M[h*2]=~M[h*2];
		M[h*2+1]=~0;
	}
	// ABOVE COPIED FROM GetImage
}


int	PreshiftedColourIncarnation::TouchDraw(short x, short y, long *Store)
{ return 0; }

void	PreshiftedColourIncarnation::Draw(short x, short y, long *Store)
{
	x-=HotX;
	y-=HotY;
	unsigned short shift = x&15;
	short LineSpacing=BytesPerLine[Pages->Current().Rez()];
	long *At = (long*)(Pages->Location() + y*LineSpacing+((x>>4)<<3));
#ifdef NOASM
	unsigned short *D=Data[shift];
	unsigned short *M=Mask[shift];

	unsigned long m;

	*(Store++)=(long)At;
	*(Store++)=(long)height-1;
	for (int h=0; h<height; h++) {
		m=*(M++);

		*(Store++)=At[0];
		*(Store++)=At[1];
		*(Store++)=At[2];
		*(Store++)=At[3];

		At[0]=At[0] & m | *(D++);
		At[1]=At[1] & m | *(D++);
		At[2]=At[2] & m | *(D++);
		At[3]=At[3] & m | *(D++);

		At=(long*)((long)At+LineSpacing);
	}
#else
	asm("
	movel	%2,%3@+		| Save output address
	subl	#1,%4
	movel	%4,%3@+		| Save Height-1
	subl	#12,%5
0:
|	Get mask	HH in d3, LL in d1
	movel	%0@+,d1
	movel	d1,d3
	swap	d1
	movew	d1,d0
	movew	d3,d1
	movew	d0,d3

|:	First word, bitplanes 0 & 1
	movel	%1@+,d0
	movel	%2@,d4
	movel	d4,%3@+
	andl	d3,d4
	orl	d0,d4
	movel	d4,%2@+

|:	First word, bitplanes 2 & 3
	movel	%1@+,d0
	movel	%2@,d4
	movel	d4,%3@+
	andl	d3,d4
	orl	d0,d4
	movel	d4,%2@+

|:	Second word (second mask), bitplanes 0 & 1
	movel	%1@+,d0
	movel	%2@,d4
	movel	d4,%3@+
	andl	d1,d4
	orl	d0,d4
	movel	d4,%2@+

|:	Second word (second mask), bitplanes 2 & 3
	movel	%1@+,d0
	movel	%2@,d4
	movel	d4,%3@+
	andl	d1,d4
	orl	d0,d4
	movel	d4,%2@

	addl	%5,%2	| next line on screen

	dbra	%4,0b
" :
  :	"a" (Mask[shift]),
	"a" (Data[shift]),
	"a" (At),
	"a" (Store),
	"d" (height),
	"d" ((long)LineSpacing)
  :	"d0", "d1", "d3", "d4"
);
#endif
}

void	PreshiftedColourIncarnation::Wipe(long *Store)
{
	SimpleWipe(Store);
}


void	PreshiftedColourIncarnation::GetImage(Screen& S, int x, int y)
{
	short LineSpacing=BytesPerLine[S.Rez()];
	unsigned short *At=(unsigned short*)(S.Location()+y*LineSpacing+x/16*8);
	unsigned short or=0,*D,*M,*SD,*SM;
	unsigned long t;
	int h,bp;

	D=Data[0];
	M=Mask[0];
	for (h=0; h<height; h++) {
		*D++=At[0];
		*D++=At[1];
		*D++=At[2];
		*D++=At[3];
		*D++=0;
		*D++=0;
		*D++=0;
		*D++=0;
		*M=(At[0]|At[1]|At[2]|At[3]);
		or|=*M++;
		*M++=0;
		At=(unsigned short*)((long)At+LineSpacing);
	}
	width=LeftBits(or);

	for (int s=1; s<16; s++) {
		D=Data[0];
		M=Mask[0];

		SD=Data[s];
		SM=Mask[s];

		for (h=0; h<height; h++) {
			for (bp=0; bp<4; bp++) {
				t=(long)D[bp] << (16-s);
				SD[bp]=t>>16;
				SD[bp+4]=t&0xffff;
			}
			SD+=8;
			D+=8;

			t=((unsigned long)*M++ << (16-s));
			M++;
			*SM++=~(t>>16);
			*SM++=~(t&0xffff);
		}
	}

	M=Mask[0];
	for (h=0; h<height; h++) {
		M[h*2]=~M[h*2];
		M[h*2+1]=~0;
	}
}


short MobileSprite::Move()
{
	short Result=0;

	MoveBy(xi,yi);
	if (X()<MinX) {
		switch (AtEdge[3]) {
			case Bounce:	xi=-xi; MoveTo(MinX,Y());
					if (Bounciness!=256) {
						xi=long(xi*Bounciness) >> 8;
					}
		break;	case Wrap:	if (X()<MinX) MoveTo(MaxX,Y());
					else MoveTo(MinX,Y());
		break;	case Stop:	MoveBy(-xi,0); xi=0;
		break;	case Watch:	;
		}
		Result|=HitWest;
	} else if (X()+Width()>MaxX) {
		switch (AtEdge[2]) {
			case Bounce:	xi=-xi; MoveTo(MaxX-Width(),Y());
					if (Bounciness!=256) {
						xi=long(xi*Bounciness) >> 8;
					}
		break;	case Wrap:	if (X()<MinX) MoveTo(MaxX,Y());
					else MoveTo(MinX,Y());
		break;	case Stop:	MoveBy(-xi,0); xi=0;
		break;	case Watch:	;
		}
		Result|=HitEast;
	}
	if (Y()<MinY) {
		switch (AtEdge[0]) {
			case Bounce:	yi=-yi; MoveTo(X(),MinY);
					if (Bounciness!=256) {
						yi=long(yi*Bounciness) >> 8;
					}
		break;	case Wrap:	if (Y()<MinY) MoveTo(X(),MaxY);
					else MoveTo(X(),MinY);
		break;	case Stop:	MoveBy(0,-yi); yi=0;
		break;	case Watch:	;
		}
		Result|=HitNorth;
	} else if (Y()+Height()>MaxY) {
		switch (AtEdge[1]) {
			case Bounce:	yi=-yi; MoveTo(X(),MaxY-Height());
					if (Bounciness!=256) {
						yi=long(yi*Bounciness) >> 8;
					}
		break;	case Wrap:	if (Y()<MinY) MoveTo(X(),MaxY);
					else MoveTo(X(),MinY);
		break;	case Stop:	MoveBy(0,-yi); yi=0;
		break;	case Watch:	;
		}
		Result|=HitSouth;
	}
	return Result;
}

void	MobileSprite::Rebound(short Walls)
{
	if (Walls&HitEast && xi>0  ||  Walls&HitWest && xi<0) { 
		xi=-xi; MoveBy(xi,0);
		if (Bounciness!=256) {
			xi=long(xi*Bounciness) >> 8;
		}
	}
	if (Walls&HitNorth && yi<0  ||  Walls&HitSouth && yi>0) {
		yi=-yi; MoveBy(0,yi);
		if (Bounciness!=256) {
			yi=long(yi*Bounciness) >> 8;
		}
	}
}

void	MobileSprite::AtBoundary(BoundaryEffect b, short Bouncy=256)
{
	for (int i=0; i<4; i++) AtEdge[i]=b;
	Bounciness=Bouncy;
}

void	MobileSprite::AtBoundary(BoundaryEffect N,
				BoundaryEffect S,
				BoundaryEffect E,
				BoundaryEffect W,
				short Bouncy=256)
{
	AtEdge[0]=N;
	AtEdge[1]=S;
	AtEdge[2]=E;
	AtEdge[3]=W;
	Bounciness=Bouncy;
}
