*\  :ts=8 bk=0
*
* ES.asm:	A neato star program based on some Euro code (from the group
*		"Absence" I believe).
*
*		This code came from Portal under the name STARTOY.LZH and
*		was uploaded by Greg Cunningham (author of BaudBandit).
*		The program was disassembled using ReSource 4.16 (from The
*		Puzzle Factory), and modified from there by me.
*
*		v2.0 Improvements:
*		o Now compatible with the A4000, AA chipset, and v3.0.
*		o Menu button no longer lunches screen.
*		o Completely new point transformation code.
*		o New star rendering code.
*		o Code speed MEGA-improved over European original
*		  (so there, nyah, pthbthbpttt!! :-) :-) :-) :-) ).
*		o Much better performance on a plain 68000-based system.
*		o Sine table resolution increased, yielding finer rotation.
*		o Odd angles now supported.
*		o Smarter about overscan.
*		o Parses Workbench ToolTypes.
*
*		v1.0 Improvements:
*		o No longer Disable()s the world whle running.
*		o Tossed out hard-coded custom copper list; now uses an
*		  Intuition screen.  (Drag it!  Amaze your friends!)
*		o Uses Intuition Window to collect mouse click.
*		o Now *closes* GfxBase.
*		o Clipping improved.
*		o Replaced 800-entry sinetable with 1024-entry table.
*		o Angles and spins now forced to even values.
*		o New, more traditional perspective formula.
*		o Reasonably commented.
*		o Busy-wait for bottom-of-frame replaced with copper
*		  interrupt.
*		o New amazing stupendous incredible argument parser.
*		o Code speed improved slightly (!)
*		o Has an ARexx port!!!!!
*
* Some dedicated EuroHacker				91??.??
* Leo L. Schwab			(415) 903-9321		9109.12
*  New argument parsing					9205.09
*  New 1024-entry sinetable				9205.09
*  Functional ARexx					9205.10
*  BOF copper interrupt					9205.11
*  1.1 enhancements completed (unreleased)		9210.11
*  Workbench ToolTypes added, parser bug fixed		9210.18
*/

****************************************************************************
* Documentation.
*
******* ES ******************************************
*
*   NAME
*	ES -- EuroStars
*
*   SYNOPSIS
*	ES [MRS][XYZ] <value> [<value> ... ]
*	ES B
*	ES Q	(ARexx only)
*
*   DESCRIPTION
*	ES is a starfield program based on European code that has been
*	massively cleaned up and featurized.  As a result, it doesn't run
*	quite as smoothly on vanilla 68000-based systems, but it's less
*	likely to trash your system.
*
*	ES may be run from the command line or from the Workbench.  When
*	you're finished looking, click either mouse button anywhere on the
*	starfield to exit.
*
*	ES lets you specify how the stars move and rotate.  This is done
*	from the command line, Workbench ToolTypes, or from the ARexx port.
*	For all, the argument syntax is the same.
*
*   ARGUMENTS
*	The M, R, and S operators specify star movement, rotation, and spin
*	respectively.  Movement specifies how many world units the stars are
*	translated each frame.  Rotation specifies a static/initial rotation
*	angle.  Spin specifies the change in rotation for each frame.  The
*	X, Y, and Z specifiers indicate which axes the operators are to
*	apply.  Rotation angles are specified in "EuroHackerGrads."  There
*	are 2048 EHG to a full 360 degree circle.
*
*	The MRS operators must appear before the the XYZ specifiers, and
*	there may be no spaces between them.  Only one MRS operator may
*	appear before a given set of XYZ specifiers.  Following the MRSXYZ
*	command are the actual values to be used.
*
*	It's much easier to understand if you look at the examples.
*
*	In addition to the above, there are two additional arguments that
*	may be used:
*
*	B:	Operate as background server.
*		Ordinarily, ES will exit when you click on its screen.
*		However, if you want to do extensive ARexx operations
*		without user interference, this option may be specified,
*		causing ES to ignore all subsequent mouse events on its
*		screen.  Once backgrounded, ES cannot be un-backgrounded.
*		The only way to terminate a backgrounded ES is with the
*		following command:
*
*	Q:	Quit.
*		When sent as an ARexx command, ES will exit immediately.
*		This option on the CLI command line is meaningless; if
*		present, the entire command line is ignored.
*
*   WORKBENCH
*	ES recognizes Workbench ToolTypes when started from Workbench.  The
*	ToolType strings are identical to the command line arguments.
*	Multiple ToolType strings may be present in the icon; each one will
*	be processed in turn.  Project icons and multiple icon selection are
*	also supported; thus, you can have several "canned" starfields ready
*	to go from a Workbench window.
*
*	Like ARexx commands (see below), the effect of multiple command
*	strings is cumulative.
*
*   AREXX
*	ES features an ARexx port.  The name of the port is EUROSTARS.
*	Commands sent to this port are exactly the same as arguments on the
*	command line.  Malformed commands are returned with RC set to
*	RC_ERROR (10).
*
*	Commands are cumulative; that is, a move command (for example)
*	remains in effect until reset by another move command for the same
*	axis.  Thus, an MX command followed later by an SX command does not
*	cancel the MX command.
*
*   EXAMPLES
*	ES mx 4
*		Move stars 4 units along X axis each frame.
*	ES sxyz 2 -4 6
*		For each frame, spin the X axis 2 EHG, the Y axis -4 EHG,
*		and the Z axis 6 EHG.
*	ES rx 512
*		Rotate X axis 512 EHG and keep it there.
*	ES rx -512 sx 2
*		Start X axis rotated at -512 EHG and add 2 EHG to it each
*		frame.
*	ES mxz 10 0 sxyz 1 1 1
*		Move 10 units along X, zero units along Z, and spin all axes
*		by one EHG each frame.
*	ES mxz sxyz 10 0 1 1 1
*		Identical to the above.
*	ES sxmxsymzsz 1 10 1 0 1
*		Again, identical to the above.
*
*	As you can see, the syntax is very flexible.
*
*   NOTES
*	Despite its nomenclature, the 'B' option does not fork ES into the
*	background; you must use the 'Run' command.
*
*	The coordinate system is right-handed, X axis horizontal, Y axis
*	vertical, Z axis perpendicular to the screen.  Positive rotations
*	are clockwise, and are in Z,Y,X order.
*
*	The default startup values are:
*	MX 0  MY 0  MZ 4   RX 0  RY 0  RZ 0   SX 0  SY 0  SZ 0
*
*   BUGS
*	Though vastly improved for v2.0, it's still a bit slow on 68000-
*	based systems.
*
*	Malformed commands generate a diagnostic string to the CLI.  These
*	strings are not available to ARexx programs or if the program is
*	started from Workbench.  The latter can be particularly annoying, as
*	malformed ToolType strings will cause ES to silently refuse to
*	start.  Sigh...
*
*   AUTHOR
*	Leo L. Schwab  --  New Technologies Group, Inc.
*	BIX:		ewhac
*	Portal:		ewhac
*	InterNet:	ewhac@ntg.com   ..or..   ewhac@well.sf.ca.us
*
****************************************************************************
* Includes.
*
		include	'exec/types.i'
		include	'exec/memory.i'
		include	'graphics/gfxbase.i'
		include	'hardware/intbits.i'
		include	'intuition/intuition.i'
		include	'libraries/dosextens.i'
		include	'workbench/startup.i'
		include	'workbench/workbench.i'
		include	'rexx/storage.i'
		include	'rexx/errors.i'


****************************************************************************
* A couple of simple macros.
*
xlib	MACRO
		XREF	_LVO\1
	ENDM

jsrlib	MACRO
		jsr	_LVO\1(a6)
	ENDM


****************************************************************************
* External references.
*
		xlib	AllocMem
		xlib	FreeMem
		xlib	AllocSignal
		xlib	FreeSignal
		xlib	AddIntServer
		xlib	RemIntServer
		xlib	OpenLibrary
		xlib	CloseLibrary
		xlib	Forbid
		xlib	Permit
		xlib	WaitPort
		xlib	PutMsg
		xlib	GetMsg
		xlib	ReplyMsg
		xlib	AddPort
		xlib	RemPort
		xlib	FindTask
		xlib	Signal
		xlib	Wait
		xlib	OpenScreen
		xlib	OpenWindow
		xlib	CloseScreen
		xlib	CloseWindow
		xlib	SetPointer
		xlib	MakeScreen
		xlib	GetScreenData
		xlib	RethinkDisplay
		xlib	QueryOverscan
		xlib	LoadRGB4
		xlib	FreeCopList
		xlib	UCopperListInit
		xlib	CWait
		xlib	CMove
		xlib	CBump
		xlib	Output
		xlib	Write
		xlib	CreateProc
		xlib	Lock
		xlib	CurrentDir
		xlib	GetDiskObject
		xlib	FreeDiskObject

		xref	_intreq


****************************************************************************
* Public symbols (primarily for debugging).
*
		xdef	eventloop
		xdef	Transform
		xdef	NextStar
		xdef	GenMat
		xdef	StarSeg
		xdef	MoveStars
		xdef	DrawStars
		xdef	SinCos
		xdef	ParseArgs
		xdef	delta_x
		xdef	Matrix
		xdef	Screen1_3
		xdef	Screen2_0


****************************************************************************
* Symbol Definitions
*
MINBOX		equ	-450
MAXBOX		equ	450
BOXRANGE	equ	MAXBOX-MINBOX+1

NSTARS		equ	70		; Must be even

MAGIC		equ	(256<<8)
ZPULL		equ	780

SPRBUFSIZ	equ	12


****************************************************************************
* And now for the code!
*
		section	EuroStars,code

CreatedSix				; Hi, Stan!
		bsr	ClearBSS	; Initialize uninitialized data

		moveq	#0,d7		; No error
		move.l	a0,a2		; Save CLI command line data
		move.l	d0,d2

	;------	Open DOS.
		move.l	(4).w,a6
		lea	DOSName(pc),a1
		moveq	#0,d0
		jsrlib	OpenLibrary
		move.l	d0,DOSBase
		beq	err_dos

	;------	Test CLI vs. Workbench.
		sub.l	a1,a1		; Find ourselves
		jsrlib	FindTask
		move.l	d0,mpsigtask
		move.l	d0,a0
		tst.l	pr_CLI(a0)	; Are we a CLI?
		beq.s	WBench		; No, don't parse command line.

	;------	CLI startup.  Parse arguments.
		move.l	a2,a0		; Restore CLI command data
		clr.b	0(a0,d2.l)	; Force NULL at end of command line
		bsr	ParseArgs
		bra.s	errcheck

	;------	Workbench startup.  Get startup message.
WBench		lea	pr_MsgPort(a0),a2
		move.l	a2,a0
		jsrlib	WaitPort
		move.l	a2,a0
		jsrlib	GetMsg
		move.l	d0,WBStartMsg
		bsr	ParseWBArgs

	;------	Now then, did the parser return an error?
errcheck	tst.l	d0
		ble.s	1$		; (Ignore 'QUIT')
		bsr	PrintCLIStr	; Print the error
		bra	err_nomemspr	; Exit (closing DOS)
1$
	;------	Allocate memory for blank sprite.
		moveq	#SPRBUFSIZ,d0
		move.l	#MEMF_CHIP!MEMF_CLEAR,d1
		jsrlib	AllocMem
		move.l	d0,sprdat
		beq	err_nomemspr

	;------	Allocate signal for message port.
		moveq	#-1,d0
		jsrlib	AllocSignal
		move.b	d0,mpsigbit
		bmi	err_signal

	;------	Open Graphics
		lea	GfxName(pc),a1
		moveq	#0,d0
		jsrlib	OpenLibrary
		move.l	d0,GBase
		beq	err_gfx

	;------	Open Intuition
		lea	IntuiName(pc),a1
		moveq	#0,d0
		jsrlib	OpenLibrary
		move.l	d0,IBase
		beq	err_intui

	;------	Open screen.  This is done differently based on OS version.
		move.l	d0,a6
		lea	TransformBuff,a3	; Procure a random buffer...
		cmp.w	#37,LIB_VERSION(a6)	; What version?
		blt.s	Screen1_3

	;------	Version 2.0.  Open OSCAN_MAX screen.
Screen2_0	move.l	a3,a1		; Use for Rectangle struct
		moveq	#OSCAN_MAX,d0
		sub.l	a0,a0		; Zero is LORES_KEY
		jsrlib	QueryOverscan

		lea	scr_def(pc),a0
		move.w	ra_MinX(a3),ns_LeftEdge(a0)
		move.w	ra_MinY(a3),ns_TopEdge(a0)
		move.w	ra_MaxX(a3),d0	; Yes, this is rather the long
		sub.w	ra_MinX(a3),d0	;  way 'round...
		addq.w	#1,d0
		move.w	ra_MaxY(a3),d1
		sub.w	ra_MinY(a3),d1
		addq.w	#1,d1
		move.w	d0,ns_Width(a0)
		move.w	d1,ns_Height(a0)
		asr.w	#1,d0
		asr.w	#1,d1
		move.w	d0,CenterX
		move.w	d1,CenterY

		jsrlib	OpenScreen	; scr_def still in A0
		move.l	d0,scrptr
		beq	err_scr
		move.l	d0,a2
		bra	OpenWin

	;------	Version 1.3.  Interrogate Workbench screen size.
Screen1_3	move.l	a3,a0		; Screen buffer
		move.l	#sc_SIZEOF,d0	; bufsiz
		moveq	#WBENCHSCREEN,d1	; Screen type
		sub.l	a1,a1		; NULL
		jsrlib	GetScreenData	; Tell me about the Workbench screen

	;------	Scale Workbench dimensions down to lores non-lace pixels.
		move.w	sc_ViewPort+vp_DWidth(a3),d0
		btst.b	#7,sc_ViewPort+vp_Modes(a3)	; Check HIRES bit
		beq.s	1$		; HIRES?
		asr.w	#1,d0		; Yes, divide width by two
1$		move.w	d0,sc_Width(a3)

		move.w	sc_ViewPort+vp_DHeight(a3),d0
		btst.b	#2,sc_ViewPort+vp_Modes+1(a3)	; Check LACE bit
		beq.s	2$		; LACE?
		asr.w	#1,d0		; Yes, divide height by two
2$		move.w	d0,sc_Height(a3)

	;------	If Workbench is bigger than us, we upgrade to its size.
		move.w	scrwide(pc),d0
		cmp.w	sc_Width(a3),d0
		bge.s	3$
		move.w	sc_Width(a3),d0
3$		move.w	d0,scrwide
		asr.w	#1,d0
		move.w	d0,CenterX

		move.w	scrhigh(pc),d0
		cmp.w	sc_Height(a3),d0
		bge.s	4$
		move.w	sc_Height(a3),d0
4$		move.w	d0,scrhigh
		asr.w	#1,d0
		move.w	d0,CenterY

	;------	Actually open the Screen.
		lea	scr_def(pc),a0
		jsrlib	OpenScreen
		move.l	d0,scrptr
		beq	err_scr
		move.l	d0,a2

	;------	Shove screen's ViewPort into nice-ish overscan position
	;------	based on Workbench screen's size.
		move.w	scrwide(pc),d0
		sub.w	sc_Width(a3),d0
		asr.w	#1,d0		; Divide difference by two
		sub.w	d0,sc_ViewPort+vp_DxOffset(a2)	; Shift screen

		move.w	scrhigh(pc),d0
		sub.w	sc_Height(a3),d0
		asr.w	#1,d0
		sub.w	d0,sc_ViewPort+vp_DyOffset(a2)	; Shift screen

	;------	Open window on screen.
OpenWin		lea	windef(pc),a0
		move.w	scrwide(pc),nw_Width(a0)
		move.w	scrhigh(pc),nw_Height(a0)
		jsrlib	OpenWindow
		move.l	d0,winptr
		beq	err_win

	;------	Set invisible pointer.
		move.l	d0,a0
		move.l	sprdat(pc),a1
		moveq	#0,d0
		moveq	#0,d1
		moveq	#0,d2
		moveq	#0,d3
		jsrlib	SetPointer

	;------	Set local bitplane pointers.
		move.l	sc_BitMap+bm_Planes+0(a2),Plane1ptr
		move.l	sc_BitMap+bm_Planes+4(a2),Plane2ptr

	;------	Allocate memory for Y offset table.
		moveq	#0,d0
		move.w	scrhigh(pc),d0
		add.w	d0,d0
		moveq	#0,d1
		move.l	(4).w,a6
		jsrlib	AllocMem
		move.l	d0,YOffTable
		beq	err_nomemtab

		bsr	GenYOffs

	;------	Add Bottom-of-frame copper interrupt.
		lea	MyCopList,a3
		move.l	a3,sc_ViewPort+vp_UCopIns(a2)	; vp->UCopIns = cl;
		move.l	a3,a0		; CINIT (cl, 4);
		moveq	#4,d0
		move.l	GBase(pc),a6
		jsrlib	UCopperListInit

		move.l	a3,a1		; CWAIT (cl, scrhigh-1, 0);
		moveq	#0,d0
		move.w	scrhigh(pc),d0
		subq.w	#1,d0		; Back it off just slightly
		moveq	#0,d1
		jsrlib	CWait
		move.l	a3,a1
		jsrlib	CBump

		move.l	a3,a1		; CMOVE (cl, intreq,
		move.l	#_intreq,d0	;	 INTF_SETCLR | INTF_COPER);
		move.l	#INTF_SETCLR!INTF_COPER,d1
		jsrlib	CMove
		move.l	a3,a1
		jsrlib	CBump

		move.l	a3,a1		; CEND (cl);
		move.l	#10000,d0
		move.l	#255,d1
		jsrlib	CWait
		move.l	a3,a1		; Not sure this is necessary, but the
		jsrlib	CBump		;  gfxmacros.h macro does it...

	;------	Recompose display.
		move.l	a2,a0		; Screen still in A2
		move.l	IBase(pc),a6
		jsrlib	MakeScreen
		jsrlib	RethinkDisplay

	;------	Load colors.
		lea	sc_ViewPort(a2),a0
		lea	colors(pc),a1
		moveq	#4,d0
		move.l	GBase(pc),a6
		jsrlib	LoadRGB4

	;------	Create sub-process to actually render stars.
		move.l	mpsigtask(pc),a0
		moveq	#0,d2
		move.b	LN_PRI(a0),d2	; Priority
		subq.l	#1,d2		; (Nudge *BELOW* creator)
		lea	StarSeg(pc),a0
		move.l	a0,d3		; SegList pointer
		lsr.l	#2,d3		; (Convert to BPTR)
		lea	ESName(pc),a0
		move.l	a0,d1		; Process name
		move.l	#4096,d4	; Stack size
		move.l	DOSBase(pc),a6
		jsrlib	CreateProc
		move.l	d0,d1
		beq	err_proc

		sub.l	#pr_MsgPort,d1	; Compute address of task
		move.l	d1,StarTask	; Store it

	;------	Launch subprocess with startup message.
		move.l	d0,a0
		lea	startmsg(pc),a1
		move.l	(4).w,a6
		jsrlib	PutMsg

	;------	Publish message port.
		lea	rexxport(pc),a1
		jsrlib	AddPort


	;------	Wait for ARexx and Intuition events.
		bsr	HandleEvents


	;------	Remove port and flush all pending messages.
		jsrlib	Forbid		; STOP!
		lea	rexxport(pc),a2
		move.l	a2,a1
		jsrlib	RemPort		; Pull port from list
		moveq	#RC_FATAL,d2

flushloop	move.l	a2,a0
		jsrlib	GetMsg		; Get message, if any
		tst.l	d0
		beq.s	1$		; No message, leave loop
		move.l	d0,a1
		move.l	d2,rm_Result1(a1)	; Die die die
		clr.l	rm_Result2(a1)
		jsrlib	ReplyMsg	; Reply failed message
		bra.s	flushloop
1$
		jsrlib	Permit		; Okay, go ahead.

	;------	Send kill signal to stars process.
		moveq	#0,d0
		bset	#SIGBREAKB_CTRL_C,d0
		move.l	StarTask(pc),a1
		jsrlib	Signal

	;------	Wait for startup message to return.
		lea	rexxport(pc),a0
		jsrlib	WaitPort


	;------	Cleanup and exit.
err_proc	move.l	GBase(pc),a6	; Delete UCopList by hand
		move.l	scrptr(pc),a0	;  (required for 1.3)
		lea	sc_ViewPort+vp_UCopIns(a0),a0
		move.l	(a0),a1
		clr.l	(a0)		; Keep system from freeing it, too
		move.l	ucl_FirstCopList(a1),a0
		jsrlib	FreeCopList

		move.l	(4).w,a6	; Delete Y Offset table
		move.l	YOffTable(pc),a1
		moveq	#0,d0
		move.w	scrhigh,d0
		add.w	d0,d0
		jsrlib	FreeMem
err_nomemtab	move.l	IBase(pc),a6	; Close window
		move.l	winptr(pc),a0
		jsrlib	CloseWindow
err_win		move.l	scrptr(pc),a0
		jsrlib	CloseScreen
err_scr		move.l	a6,a1
		move.l	(4).w,a6
		jsrlib	CloseLibrary	; Close Intuition
err_intui	move.l	GBase(pc),a1
		jsrlib	CloseLibrary	; Close Graphics
err_gfx		moveq	#0,d0
		move.b	mpsigbit(pc),d0
		jsrlib	FreeSignal	; Free msgport signal
err_signal	move.l	sprdat(pc),a1
		moveq	#SPRBUFSIZ,d0
		jsrlib	FreeMem
err_nomemspr	move.l	DOSBase(pc),a1
		jsrlib	CloseLibrary
err_dos
		move.l	WBStartMsg(pc),d0	; Reply Workbench startup
		beq.s	xit			;  if present.

		jsrlib	Forbid		; Prevent Workbench from unloading
		move.l	d0,a1		;  us before we've actually exited
		jsrlib	ReplyMsg
		clr.l	d0

xit		rts


****************************************************************************
* ARexx message and window event processing loop.
*
HandleEvents
	;------	Compute signal flag for window port.
		moveq	#0,d2
		move.l	winptr(pc),a0
		move.l	wd_UserPort(a0),a0
		move.b	MP_SIGBIT(a0),d0
		bset.l	d0,d2

	;------	Compute signal flag for ARexx port.
		move.b	mpsigbit(pc),d0
		bset.l	d0,d2

	;------	Wait for something to happen.
eventloop	move.l	d2,d0
		jsrlib	Wait

	;------	Check for ARexx messages.
		moveq	#RC_ERROR,d3

0$		lea	rexxport(pc),a0
		jsrlib	GetMsg
		tst.l	d0		; Message present?
		beq.s	9$		; No, fall off

		move.l	d0,a2
		move.l	ARG0(a2),a0	; Get argument string
		bsr	ParseArgs	; Parse it
		move.l	d0,d7		; Was there an error?
		ble.s	1$		; No

		move.l	d3,rm_Result1(a2)	; Yes, store error
		bra.s	2$

1$		clr.l	rm_Result1(a2)
2$		clr.l	rm_Result2(a2)	; Result2 always NULL
		move.l	a2,a1
		jsrlib	ReplyMsg	; Send message back
		bra.s	0$		; Next message, please
9$
	;------	Was that parser return a REXX request for death?
		tst.l	d7
		bmi.s	Rexxit		; Why, yes it was!

	;------	Check for window events.
		move.l	winptr(pc),a0
		move.l	wd_UserPort(a0),a0
		jsrlib	GetMsg		; Check port
		tst.l	d0		; Something there?
		beq.s	eventloop	; No, go to sleep
		move.l	d0,a1		; Yup, reply it
		jsrlib	ReplyMsg

	;------	Window event arrived.  If we are a background server, ignore
	;------	the event.  Elsewise, return.
		move.w	BackServ(pc),d0	; Just to set flags
		bne.s	eventloop

Rexxit		rts			; User wants us dead, dammit.


****************************************************************************
* Welcome to the actual stars code.
*
		cnop	0,4

StarSeg		dc.l	0		; Phony NextSeg pointer

	;------	Entry point.
		move.l	(4).w,a6
		sub.l	a1,a1
		jsrlib	FindTask		; Get pointer to this task.
		move.l	d0,a0
		lea	pr_MsgPort(a0),a2	; Get pointer to MsgPort

	;------	Wait for startup message.
		move.l	a2,a0
		jsrlib	WaitPort
		move.l	a2,a0
		jsrlib	GetMsg
		move.l	d0,StarMsg

	;------	Compute sigmask.
		moveq	#0,d0
		bset.l	#SIGBREAKB_CTRL_C,d0
		bset.l	#SIGBREAKB_CTRL_F,d0	; BOF will happen here.
		move.l	d0,StarSigMask

	;------	Install interrupt server.
		moveq	#INTB_COPER,d0
		lea	BOFintrnode(pc),a1
		jsrlib	AddIntServer

	;------	Main star loop.
mainloop	bsr	AddSpins
		bsr	GenMat
		bsr	Transform
		bsr.s	MoveStars

		move.l	StarSigMask(pc),d0
		jsrlib	Wait		; Wait for BOF or kill signal
		move.l	d0,StarSigs

		bsr	EraseStars	; Do this in any case
		bsr	DrawStars

		move.l	StarSigs(pc),d0
		btst.l	#SIGBREAKB_CTRL_C,d0
		beq.s	mainloop

	;------	Got kill signal from above.  Remove interrupt server,
	;------	reply startup and exit.
		jsrlib	Forbid		; HA!  MINE!
		moveq	#INTB_COPER,d0
		lea	BOFintrnode(pc),a1
		jsrlib	RemIntServer

		move.l	StarMsg(pc),a1
		jmp	_LVOReplyMsg(a6)	; Poof!  Bye...



****************************************************************************
* Move the stars
*
MoveStars	lea	XCoords(pc),a0
		lea	YCoords(pc),a1
		lea	ZCoords(pc),a2
		move.w	delta_x(pc),d0
		move.w	delta_y(pc),d1
		move.w	delta_z(pc),d2
		move.w	#BOXRANGE,d4
		moveq	#NSTARS-1,d7

0$		move.w	(a0),d3
		add.w	d0,d3		; Add move delta
		cmp.w	#MAXBOX,d3	; Too big?
		bgt.s	1$
		cmp.w	#MINBOX,d3	; Too small?
		bge.s	2$
		add.w	d4,d3		; Wrap up
		bra.s	2$
1$		sub.w	d4,d3		; Wrap down
2$		move.w	d3,(a0)+

		move.w	(a1),d3
		add.w	d1,d3
		cmp.w	#MAXBOX,d3
		bgt.s	11$
		cmp.w	#MINBOX,d3
		bge.s	22$
		add.w	d4,d3
		bra.s	22$
11$		sub.w	d4,d3
22$		move.w	d3,(a1)+

		move.w	(a2),d3
		add.w	d2,d3
		cmp.w	#MAXBOX,d3
		bgt.s	111$
		cmp.w	#MINBOX,d3
		bge.s	222$
		add.w	d4,d3
		bra.s	222$
111$		sub.w	d4,d3
222$		move.w	d3,(a2)+

		dbra	d7,0$

		rts


****************************************************************************
* Star rendering routines.
*
* This too has undergone a re-write since 1.0.  A BSET instruction is now
* used to plot the star directly into the bitmap.  The Y-offset into the
* bitmap is now fetched from a pre-computed table to save on a multiply.
* A change to the in-memory representation of the projected points saved
* a few cycles in memory fetches.  Further, rendering is now done with bytes
* rather than words (since BSETs to memory are only byte-wide); any loss on
* higher-order processors is made up for by the faster clock.
*
* EraseStars underwent a kooky change since it appears to be the blocking
* factor for clean rendering on a 68000.  Getting even faster would mean
* major towering, which falls under the law of diminshing returns.
*
EraseStars	moveq	#0,d0
		move.l	Plane1ptr(pc),a1
		move.l	Plane2ptr(pc),a2
		lea	PlaneOffsets,a4
		moveq	#(NSTARS>>1)-1,d7	; As the programmer :-), I
					;  guarantee the count will be even

1$		move.l	(a4)+,d1	; Grab two offsets at once
		move.b	d0,0(a1,d1.w)	; Blast entire byte
		move.b	d0,0(a2,d1.w)
		swap	d1		; Switch to other offset
		move.b	d0,0(a1,d1.w)	; Blast entire byte
		move.b	d0,0(a2,d1.w)
		dbra	d7,1$

		rts


DrawStars	lea	TransformBuff,a0	; Coords in YXZ order
		move.l	YOffTable(pc),a1
		lea	PlaneOffsets,a3
		move.l	Plane1ptr(pc),a2
		move.l	Plane2ptr(pc),a4
		moveq	#NSTARS-1,d7
		move.w	scrhigh(pc),d4
		move.w	scrwide(pc),d5
NextDraw	move.w	(a0)+,d1	; Load and check Y value
		bmi.s	clipped_y
		cmp.w	d4,d1
		bge.s	clipped_y

		move.w	(a0)+,d0	; Load and check X value
		bmi.s	clipped_x
		cmp.w	d5,d0
		bge.s	clipped_x

		move.w	d0,d2
		not.w	d0		; Invert X for BSET bit positon
		lsr.w	#3,d2		; Compute byte offset in line
		add.w	d1,d1
		move.w	0(a1,d1.w),d1	; Get Y offset from table
		add.w	d1,d2
		move.w	d2,(a3)+	; Store computed offset
		move.w	(a0)+,d6	; Load Z
		cmp.w	#-176,d6	; Z closer than this?
		ble.s	1$		; No, draw just plane 1 (dim)

		bset	d0,0(a4,d2.w)	; This plane definitely gets written
		cmp.w	#130,d6		; Very close?
		ble.s	2$		; No
1$		bset	d0,0(a2,d2.w)	;    Yes; draw brightest value
2$
		dbra	d7,NextDraw

		rts

clipped_y	addq.w	#2,a0		; Skip unread x
clipped_x	addq.w	#2,a0		; Skip unread z
		clr.w	(a3)+
		dbra	d7,NextDraw

		rts


****************************************************************************
* This is the biggie.
*
* This has been completely re-written, and where the biggest speed gains
* were realized.  It used to be three discrete rotations.  Now it's a full
* matrix operation.
*
* The source XYZ values are loaded into D0-D2.  The resulting XYZ values are
* left in D3-D5.  The matrix is stored in column-major ZYX order.  This is
* done to facilitate early rejection of Z values behind the camera (though
* I don't yet make use of that).
*
* The matrix elements are in 2.14 bit fixed-point notation.  The numbers in
* the coordinate array are straight integers.
*
Transform	lea	XCoords(pc),a0
		lea	YCoords(pc),a1
		lea	ZCoords(pc),a2
		lea	TransformBuff,a4
		moveq	#NSTARS-1,d7
		move.l	#MAGIC,d6
		move.w	#ZPULL,a5	; Being used for storage

	;------	Load vertex and matrix.
NextStar	move.w	(a0)+,d0	; Fetch X, Y, and Z
		move.w	(a1)+,d1
		move.w	(a2)+,d2
		lea	Matrix(pc),a3	; Fetch matrix

	;------	Multiply vertex through matrix.
	;------	First column is Z.
		move.w	d0,d5		; X
		muls	(a3)+,d5	;   * *mat++
		move.w	d1,d4		; Y
		muls	(a3)+,d4	;   * *mat++
		add.l	d4,d5		;		Accumulate to D5
		move.w	d2,d4		; Z
		muls	(a3)+,d4	;   * *mat++
		add.l	d4,d5		;		Accumulate to D5
		swap	d5
		rol.l	#2,d5		; D5 >>= 14;

	;------	Second column is Y.
		move.w	d0,d4		; X
		muls	(a3)+,d4	;   * *mat++
		move.w	d1,d3		; Y
		muls	(a3)+,d3	;   * *mat++
		add.l	d3,d4		;		Accumulate to D4
		move.w	d2,d3		; Z
		muls	(a3)+,d3	;   * *mat++
		add.l	d3,d4		;		Accumulate to D4
		swap	d4
		rol.l	#2,d4		; D4 >>= 14;

	;------	Third column is X.
		move.w	d0,d3		; X
		muls	(a3)+,d3	;   * *mat++
		move.w	d1,d0		; Y (original X no longer needed)
		muls	(a3)+,d0	;   * *mat++
		add.l	d0,d3		;		Accumulate to D3
		move.w	d2,d0		; Z
		muls	(a3)+,d0	;   * *mat++
		add.l	d0,d3		;		Accumulate to D3
		swap	d3
		rol.l	#2,d3		; D3 >>= 14;

****************
* Here is performed the perspective projection.
*
* Normally, this is accomplished by dividing both X and Y by the Z
* coordinate.  However, in this case, only one division is performed to
* calculate a fixed-point scaling factor, which is then multiplied by the
* X and Y values.  This is done because multiplication is cheaper than
* division.  The perspective scalar is:
*
*	    MAGIC
*	-------------
*	- (Z - ZPULL)
*
* The subtraction from Z is to "pull" the stars away from the camera (which
* is at Z == 0) so that they'll be visible.  The negation effectively flips
* the Z axis, which makes the calculation easier (trust me).  MAGIC and
* ZPULL are currently set to 256 and 780 respectively.  The 256 is a number
* I pulled out of Thin Air.  (So's the 780, for that matter...)  Feel free
* to play with them to see what happens.
*
* The projected points are stored in YXZ order.
*
	;------	Compute scalar.
		move.l	d6,d0		; MAGIC
		move.w	a5,d1		; ZPULL
		sub.w	d5,d1		;	- Z
		ble.s	BehindCamera
		divu	d1,d0		; == 256 / (ZPULL - Z)
		bvs.s	BehindCamera	; Shunt wild division

	;------	Multiply scalar by X and Y components.
		move.w	d4,d1		; Y
		muls	d0,d1		;   * 256 / (ZPULL - Z)
		lsr.l	#8,d1		; (Unavoidable.  Ack!)
		add.w	CenterY(pc),d1
		move.w	d1,(a4)+	; Y store

		move.w	d3,d1		; X
		muls	d0,d1		;   * 256 / (ZPULL - Z)
		lsr.l	#8,d1
		add.w	CenterX(pc),d1
		move.w	d1,(a4)+	; X store

		move.w	d5,(a4)+	; Z store (for pixel brightness)

		dbra	d7,NextStar

		rts

	;------	Whoops!  Behind the camera.  Force an invisible point.
BehindCamera	moveq	#-1,d1
		move.l	d1,(a4)+	; X and Y store
		move.w	d5,(a4)+	; Z store
		dbra	d7,NextStar

		rts



****************************************************************************
* Generate a matrix.
*
* This routine generates a three-rotation matrix all at once, in ZYX order.
* Rotations are anti-clockwise.
*
GenMat		move.w	theta_z(pc),d0	; Collect sines and cosines
		bsr	SinCos
		move.w	d0,d4		; sinZ
		move.w	d1,d5		; cosZ
		move.w	theta_y(pc),d0
		bsr	SinCos
		move.w	d0,d2		; sinY
		move.w	d1,d3		; cosY
		move.w	theta_x(pc),d0
		bsr	SinCos

		lea	Matrix(pc),a0	; Point at matrix

	;------	Compute first column Z.
	;------	sinX * sinZ - cosX * sinY * cosZ
		move.w	d0,d7		; sinX
		muls	d4,d7		;      * sinZ
		move.w	d1,d6		; cosX
		muls	d2,d6		;      * sinY
		swap	d6
		rol.l	#2,d6
		move.w	d6,a1		;		(Save for later)
		muls	d5,d6		; 	      * cosZ
		sub.l	d6,d7
		swap	d7
		rol.l	#2,d7		; D7 >>= 14
		move.w	d7,(a0)+	;			(Store)

	;------	sinX * cosZ + cosX * sinY * sinZ
		move.w	d0,d6		; sinX
		muls	d5,d6		;      * cosZ
		move.w	a1,d7		; cosX * sinY
		muls	d4,d7		;	      * sinZ
		add.l	d6,d7
		swap	d7
		rol.l	#2,d7
		move.w	d7,(a0)+	;			(Store)

	;------	cosX * cosY
		move.w	d1,d7		; cosX
		muls	d3,d7		;      * cosY
		swap	d7
		rol.l	#2,d7
		move.w	d7,(a0)+	;			(Store)

	;------	Compute second column Y.
	;------	cosX * sinZ + sinX * sinY * cosZ
		move.w	d1,d7		; cosX
		muls	d4,d7		;      * sinZ
		move.w	d0,d6		; sinX
		muls	d2,d6		;      * sinY
		swap	d6
		rol.l	#2,d6
		move.w	d6,a1		;		(Save for later)
		muls	d5,d6		;	      * cosZ
		add.l	d6,d7
		swap	d7
		rol.l	#2,d7
		move.w	d7,(a0)+	;			(Store)

	;------	cosX * cosZ - sinX * sinY * sinZ
		move.w	d1,d7		; cosX
		muls	d5,d7		;      * cosZ
		move.w	a1,d6		; sinX * sinY
		muls	d4,d6		;	      * sinZ
		sub.l	d6,d7
		swap	d7
		rol.l	#2,d7
		move.w	d7,(a0)+	;			(Store)

	;------	-sinX * cosY
		move.w	d0,d7		;  sinX
		neg.w	d7		; -
		muls	d3,d7		;	* cosY
		swap	d7
		rol.l	#2,d7
		move.w	d7,(a0)+	;			(Store)

	;------	Compute third column X.
	;------	cosY * cosZ
		move.w	d3,d7		; cosY
		muls	d5,d7		;      * cosZ
		swap	d7
		rol.l	#2,d7
		move.w	d7,(a0)+	;			(Store)

	;------	-cosY * sinZ
		move.w	d3,d7		;  cosY
		neg.w	d7		; -
		muls	d4,d7		;	* sinZ
		swap	d7
		rol.l	#2,d7
		move.w	d7,(a0)+	;			(Store)

	;------	sinY
		move.w	d2,(a0)+	;			(Store)

	;------	Phew!  We're outa here.
		rts


****************************************************************************
* Sine/Cosine calculator.  Nothing amazing here, just a table lookup.
*
* This got a lot more complicated for no good reason :-).  The sine/cosine
* table ranges from 0-90°.  The code selects the proper values and negates
* them based on the quadrant in which the angle lies.  2048 == 360 degrees.
* Sine table entries are represented using 14 bit fixed point fractions.
*
* Angle passed in D0.  May now be an odd number.
* Returns sine in D0, cosine in D1.
*
SinCos		move.w	d2,-(sp)

		add.w	d0,d0		; Word offset
		lea	SineTable(pc),a5
		move.w	d0,d2		; Copy
		and.w	#1024-1,d0	; Clip to 90°
		move.w	#1024,d1	; d0 == ang
		sub.w	d0,d1		; d1 == gna

		move.w	0(a5,d0.w),d0	; Fetch sine candidate
		move.w	0(a5,d1.w),d1	; Fetch cosine candidate

	;------	Determine which is sine and which is cosine.
		lsl.w	#5,d2		; Shift quadrant bits into Carry and
					;  Minus flags.
		bpl.s	1$		; First or third quadrant?
		exg	d0,d1		; Yes, exchange values
1$
	;------	Is sine negative?
		bcc.s	2$		; Third or fourth quadrants?
		neg.w	d0		; Yes, negate si(g)ne

	;------	Is cosine negative?  We test for this two ways, depending on
	;------	the result of the previous test.
		tst.w	d2		; Quadrant 3?
		bpl.s	88$		; Yes, negate cosine
		bra.s	99$

2$		tst.w	d2		; Quadrant 2?
		bpl.s	99$
88$		neg.w	d1		; Yes, negate cosine

99$		move.w	(sp)+,d2
		rts


****************************************************************************
* Add spins to current rotation angles.  Clip to 360° circle.
*
AddSpins	move.w	#2048-1,d1	; 360° == 2048 EHG
		lea	theta_x(pc),a0
		lea	spin_x(pc),a1

		move.w	(a0),d0		; Get angle
		add.w	(a1)+,d0	; Add spin
		and.w	d1,d0		; Clip to 360°
		move.w	d0,(a0)+	; Store it back

		move.w	(a0),d0
		add.w	(a1)+,d0
		and.w	d1,d0
		move.w	d0,(a0)+

		move.w	(a0),d0
		add.w	(a1),d0
		and.w	d1,d0
		move.w	d0,(a0)

		rts



****************************************************************************
* New ParseArgs.  More flexible!  Less filling!
* Added 9202.20		Finished 9205.09
*
*	A0:  Argument string.
*
*	As the options are parsed, the destination address of the value is
*	pushed on a queue.  As values are acquired, the address is pulled,
*	and the value stored there.  Imbalances are checked and reported.
*
* SYNOPSIS
*	[MRS][XYZ] <value> [ <value> ... ]
*
*	Only one M, R, or S qualifier may accompany XYZ specifiers.  They
*	may be specified in any order, and the values need not immediately
*	follow them.
*
* OPTIONS
*	M: Move; specify translation
*	R: Rotate; specify initial rotation
*	S: Spin; specify spin velocities
*

ParseArgs	movem.l	d2/d7/a2-a5,-(sp)
		sub.l	a3,a3		; Mode (NULL)
		move.l	sp,a4		; Stacksave
		move.l	sp,a5		; Pop pointer

	;------	Copy active variables to workspace.
		lea	delta_x(pc),a1
		lea	wrk_delta(pc),a2
		move.l	(a1)+,(a2)+
		move.l	(a1)+,(a2)+
		move.l	(a1)+,(a2)+
		move.l	(a1)+,(a2)+
		move.w	(a1),(a2)

	;------	Main parsing loop.
getchar		moveq	#0,d0
		move.b	(a0)+,d0	; getchar()
		beq	EndOfString
		cmp.b	#'a',d0		; Lower case?
		blo.s	0$
		sub.b	#'a'-'A',d0	; Convert to upper case
0$
	;------	Test for whitespace.
		cmp.b	#' ',d0
		beq.s	whitespace
		cmp.b	#9,d0		; TAB
		beq.s	whitespace
		cmp.b	#10,d0		; Newline
		beq.s	whitespace

	;------	Test for numeric.
		cmp.b	#'-',d0		; Leading minus
		beq.s	numeric
		cmp.b	#'0',d0
		blo.s	3$
		cmp.b	#'9',d0
		bls.s	numeric
3$
	;------	Test for [B]ackground command.
		cmp.b	#'B',d0
		bne.s	1$
		move.w	d0,BackServ	; All it has to be is non-zero
		bra	UnWindOK	; Ignore rest of string
1$
	;------	Test for [Q]uit command.
		cmp.b	#'Q',d0
		bne.s	2$
		moveq	#-1,d0		; Tell upstairs we need to quit
		bra	UnWind		; Ignore rest of string
2$
	;------	Test for '?' help.
		moveq	#ERR_HELP,d7
		cmp.b	#'?',d0
		beq	ParseError

	;------	Test for 'XYZ'.
		cmp.b	#'X',d0
		blo.s	4$
		cmp.b	#'Z',d0
		bls.s	GotXYZ
4$
	;------	Test for 'MRS'; set mode if present.
		cmp.b	#'M',d0
		bne.s	11$
		lea	wrk_delta(pc),a3	; Set mode
		bra.s	getchar
11$
		cmp.b	#'R',d0
		bne.s	22$
		lea	wrk_theta(pc),a3	; Set mode
		bra.s	getchar
22$
		cmp.b	#'S',d0
		bne	33$
		lea	wrk_spin(pc),a3		; Set mode
		bra	getchar
33$
	;------	Dunno what this is; error.
		moveq	#ERR_SYNTAX,d7
		bra	ParseError


	;------	Process whitespace.
whitespace	sub.l	a3,a3		; NULL out mode
		bra	getchar


	;------	Process numerics.
numeric		subq.w	#1,a0		; ungetchar()
		bsr.s	GatherNum	; Parsed value returned in D0

		moveq	#ERR_EXTRAVAL,d7
		cmpa.l	sp,a5		; Are there values yet to be poked?
		beq	ParseError	; Nope, too many values.
		move.l	-(a5),a1	; Get address to poke
		move.w	d0,(a1)		; Write value.

		bra	getchar


	;------	Process presence of 'XYZ'.
GotXYZ		moveq	#ERR_NOMODE,d7
		move.l	a3,d1		; Mode set?
		beq	ParseError	; No, user forgot MRS qualifier

		sub.b	#'X',d0		; Compute index
		add.w	d0,d0		; Compute word offset
		add.l	a3,d0		; Add "mode" to get final address
		move.l	d0,-(sp)	; Push address
		bra	getchar


	;------	End of string; perform sanity checks and return.
EndOfString	moveq	#ERR_MISSINGVAL,d7
		cmpa.l	sp,a5		; Is stack empty?
		bne.s	ParseError

	;------	Copy new values to active variables.
		lea	wrk_delta(pc),a0
		lea	delta_x(pc),a1
		move.l	(a0)+,(a1)+
		move.l	(a0)+,(a1)+
		move.l	(a0)+,(a1)+
		move.l	(a0)+,(a1)+
		move.w	(a0),(a1)

UnWindOK	moveq	#0,d0		; No error
UnWind		move.l	a4,sp		; Restore stack pointer
		movem.l	(sp)+,d2/d7/a2-a5
		rts			; Back to you, Brian...


****************
* Gather decimal number.
*
GatherNum	moveq	#0,d0
		moveq	#0,d1
		moveq	#0,d2		; negative flag

	;------	Gather chars.  Test for whitespace first.
numloop		move.b	(a0)+,d1
		beq.s	11$		; NULL; EOL
		cmp.b	#' ',d1
		beq.s	11$
		cmp.b	#9,d1		; TAB
		beq.s	11$
		cmp.b	#10,d1		; Newline
		beq.s	11$

	;------	Check for unary minus.
		cmp.b	#'-',d1
		beq.s	22$

	;------	Check for numeric.
		moveq	#ERR_BADVAL,d7
		cmp.b	#'0',d1
		blo.s	ParseError
		cmp.b	#'9',d1
		bhi.s	ParseError

	;------	Accumulate numeric digit.  Overflow not tested.
		mulu	#10,d0
		sub.b	#'0',d1
		add.w	d1,d0
		bra.s	numloop

	;------	Flip sense of minus sign.  We don't check for multiple
	;------	instances.
22$		not.b	d2
		bra.s	numloop

	;------	Whitespace encountered; return to parser for storage.
11$		tst.b	d2		; This number negative?
		beq.s	110$
		neg.w	d0		; Yep, turn it over
110$
		subq.w	#1,a0		; Push back char that stopped us
		rts


****************
* Short routine to report parsing errors
*
ParseError	move.l	ErrStrs(pc,d7.w),d0	; Pointer to error string.
		bra.s	UnWind


ErrStrs		dc.l	SyntaxErr,BadVal,ModeMissing,MissingVal
		dc.l	ExtraVal,HelpStr

ERR_SYNTAX	EQU	0
ERR_BADVAL	EQU	4
ERR_NOMODE	EQU	8
ERR_MISSINGVAL	EQU	12
ERR_EXTRAVAL	EQU	16
ERR_HELP	EQU	20


****************************************************************************
* Parse Workbench icons.
*
* This routine scans all icons in the sm_ArgList.  Drawer icons are skipped.
* For each icon, each ToolType string is handed to the parser in turn.  If
* any error is reported, the whole thing aborts and the program won't start
* (maybe I should throw up a requester someday, eh?)
*
ParseWBArgs	movem.l	d2/d3/d7/a2-a5,-(sp)
		moveq	#0,d7		; Clear error

	;------	Open icon.library.
		lea	IconName(pc),a1
		moveq	#0,d0
		jsrlib	OpenLibrary
		tst.l	d0
		beq.s	err_noicon
		move.l	d0,a6
		move.l	DOSBase(pc),a5

	;------	Get WBArg fields.
		move.l	WBStartMsg(pc),a2
		move.l	sm_NumArgs(a2),d2
		move.l	sm_ArgList(a2),a2

	;------	Process each WBArg in turn.
ArgLoop		move.l	wa_Name(a2),a3
		tst.b	(a3)
		beq.s	NextArg		; It's a directory icon, skip it

	;------	CurrentDir() over to icon.
		move.l	wa_Lock(a2),d1
		exg	a5,a6		; Get DOSBase
		jsrlib	CurrentDir
		exg	a5,a6		; Recover IconBase
		move.l	d0,d3		; Olddir

	;------	Tell me about the icon.
		move.l	a3,a0		; wa_Name
		jsrlib	GetDiskObject
		tst.l	d0
		beq.s	badob		; Failed; ignore it

	;------	Does this icon have any ToolType strings?
		move.l	d0,a3
		move.l	do_ToolTypes(a3),d0
		beq.s	EndStrs		; No ToolType strings, skip it
		move.l	d0,a4

	;------	Parse each ToolType string in turn.
ToolTypeLoop	move.l	(a4)+,d0
		beq.s	EndStrs		; No more strings for this icon

		move.l	d0,a0		; String
		bsr	ParseArgs
		move.l	d0,d7		; Save error
		beq.s	ToolTypeLoop	; No error; keep working

	;------	Drop icon.
EndStrs		move.l	a3,a0
		jsrlib	FreeDiskObject

	;------	Return to original location.
badob		move.l	d3,d1
		exg	a5,a6		; DOSBase
		jsrlib	CurrentDir	; Return to original directory
		exg	a5,a6		; IconBase

		tst.l	d7		; Was there a parsing error?
		bne.s	err_badwbparse	; Yes, abort arg processing

	;------	Advance to next WBArg
NextArg		addq.w	#wa_SIZEOF,a2	; Advance WBArg
		subq.w	#1,d2		; wb_NumArgs--
		bne.s	ArgLoop

	;------	Close icon.library.
err_badwbparse	move.l	a6,a1
		move.l	(4).w,a6
		jsrlib	CloseLibrary

err_noicon	move.l	d7,d0
		movem.l	(sp)+,d2/d3/d7/a2-a5
		rts


****************************************************************************
* Print string to CLI.  Pointer to string in D0.
*
PrintCLIStr	movem.l	d2/d3/a2/a6,-(sp)
		move.l	d0,a2		; Save string pointer.
		move.l	DOSBase(pc),a6
		jsrlib	Output		; Somewhere to print?
		move.l	d0,d1
		beq.s	99$		; No, forget it

		move.l	a2,d2		; Compute string length
0$		tst.b	(a2)+
		bne.s	0$
		suba.l	d2,a2
		move.l	a2,d3
		subq.l	#1,d3
		jsrlib	Write		; Write string
99$
		movem.l	(sp)+,d2/d3/a2/a6
		rts


****************************************************************************
* Bottom-of-frame interrupt routine.
*
BOFintr		moveq	#0,d0
		bset.l	#SIGBREAKB_CTRL_F,d0	; F stands for Frame
		move.l	(4).w,a6
		jsrlib	Signal			; Task ptr already in A1

		moveq	#0,d0
		rts


****************************************************************************
* Set up the screen Y offset table.
*
GenYOffs	move.w	d2,-(sp)

		move.l	scrptr(pc),a0
		move.l	sc_ViewPort+vp_RasInfo(a0),a0
		move.l	ri_BitMap(a0),a0
		move.w	bm_BytesPerRow(a0),d1
		move.w	bm_Rows(a0),d2

		move.l	YOffTable(pc),a0
		moveq	#0,d0
		subq.w	#1,d2		; Fencepost for DBRA

1$		move.w	d0,(a0)+	; Write offset
		add.w	d1,d0		; Increment to next row
		dbra	d2,1$

		move.w	(sp)+,d2
		rts


****************************************************************************
* Something I added to make things more reliable.
*
ClearBSS	move.l	#EndBSS,d2
		lea	StartBSS,a2
		sub.l	a2,d2
		subq.w	#1,d2
1$		clr.b	(a2)+
		dbra	d2,1$

		rts


****************************************************************************
* Data!  (Yes, Captain?)
*
delta_x		dc.w	0	; Star movement
delta_y		dc.w	0
delta_z		dc.w	4
theta_x		dc.w	0	; Initial/current rotation angles
theta_y		dc.w	0
theta_z		dc.w	0
spin_x		dc.w	0	; Spin velocities
spin_y		dc.w	0
spin_z		dc.w	0

wrk_delta	dc.w	0,0,0	; Working areas for the parser
wrk_theta	dc.w	0,0,0
wrk_spin	dc.w	0,0,0

CurrentSinCos	dc.w	0	; theta_x
		dc.w	0
		dc.w	0	; theta_y
		dc.w	0
		dc.w	0	; theta_z
		dc.w	0
Matrix		dc.w	0,0,0,0,0,0,0,0,0
CenterX		dc.w	0
CenterY		dc.w	0
IBase		dc.l	0
GBase		dc.l	0
DOSBase		dc.l	0
winptr		dc.l	0
Plane1ptr	dc.l	0
Plane2ptr	dc.l	0
sprdat		dc.l	0
YOffTable	dc.l	0
WBStartMsg	dc.l	0
StarMsg		dc.l	0
StarSigMask	dc.l	0
StarSigs	dc.l	0
BackServ	dc.w	0

XCoords		dc.w	$FF37,$FED2,$6A,$FFF7,$C8,$CD,$FFF9,$132,$FF35
		dc.w	$FF9A,$FF7F,$16B,$EC,$FFF7,$186,$FF5A,$FF0D,$177
		dc.w	$FEAF,$6F,$FF5F,$FF22,$150,$2B,$FED5,$FEE3,$90,$AA
		dc.w	$FEBE,$13A,$12D,$FFA4,$FF49,$FEEE,$41,$164,$FF09
		dc.w	$8A,$FFE3,$D2,$FEBE,$13A,$12D,$FFA4,$FF49,$FEEE,$41
		dc.w	$164,$FF09,$8A,$FF46,$FF85,$154,$FF19,$60,$FF5C,$9B
		dc.w	$FFA8,$FF18,$158,$AE,$FF2F,$FE72,$21,$8C,$FE8B,$CF
		dc.w	$48,$FF7A,$137,$C8,$FED4
YCoords		dc.w	$FF5F,$FF22,$150,$2B,$FED5,$FEE3,$90,$AA,$FEBE,$13A
		dc.w	$12D,$FFA4,$FF49,$FEEE,$41,$164,$FF09,$8A,$FFE3,$D2
		dc.w	$FF37,$FED2,$6A,$FFF7,$C8,$CD,$FFF9,$132,$FF35
		dc.w	$FF9A,$FF7F,$16B,$EC,$FFF7,$186,$FF5A,$FF0D,$177
		dc.w	$FEAF,$6F,$177,$FEAF,$6F,$FF5F,$FF22,$150,$2B,$FED5
		dc.w	$FEE3,$90,$164,$FF09,$8A,$FFE3,$FF52,$FF2F,$18E,$21
		dc.w	$FF74,$FE8B,$CF,$FFB8,$86,$FEC9,$D2,$FF22,$94,$14D
		dc.w	$FE9B,$F4,$C8,$12C
ZCoords		dc.w	$FF52,$FF2F,$FE72,$21,$8C,$175,$FF31,$FFB8,$FF7A
		dc.w	$137,11,$153,$FF22,$94,$14D,$FE9B,$F4,$FEEC,$9B
		dc.w	$FF46,$FF85,$154,$FF19,$60,$FF5C,$9B,$FFA8,$FF18
		dc.w	$158,$FF61,$FE8F,$F8,$FEE7,$8C,$FF85,$48,$FEAC
		dc.w	$FF17,$6A,$13B,$FF01,$FF3A,$FFF9,7,$BE,$12B,$FED1
		dc.w	$FE94,$FC,$FF91,$CD,$FFF9,$132,$FF35,$FF9A,$FF7F
		dc.w	$16B,$EC,$FFF7,$186,$FED2,$6A,$FFF7,$C8,$CD,$FFF9
		dc.w	$132,$FF35,$FF9A,$FF7F,$96,$FF06


SineTable	dc.w	0,50,101,151,201,251,302,352
		dc.w	402,452,503,553,603,653,704,754
		dc.w	804,854,904,955,1005,1055,1105,1155
		dc.w	1205,1255,1306,1356,1406,1456,1506,1556
		dc.w	1606,1656,1706,1756,1806,1856,1906,1956
		dc.w	2006,2055,2105,2155,2205,2255,2305,2354
		dc.w	2404,2454,2503,2553,2603,2652,2702,2752
		dc.w	2801,2851,2900,2949,2999,3048,3098,3147
		dc.w	3196,3246,3295,3344,3393,3442,3492,3541
		dc.w	3590,3639,3688,3737,3786,3835,3883,3932
		dc.w	3981,4030,4078,4127,4176,4224,4273,4321
		dc.w	4370,4418,4467,4515,4563,4612,4660,4708
		dc.w	4756,4804,4852,4900,4948,4996,5044,5092
		dc.w	5139,5187,5235,5282,5330,5377,5425,5472
		dc.w	5520,5567,5614,5661,5708,5756,5803,5850
		dc.w	5897,5943,5990,6037,6084,6130,6177,6223
		dc.w	6270,6316,6363,6409,6455,6501,6547,6593
		dc.w	6639,6685,6731,6777,6823,6868,6914,6960
		dc.w	7005,7050,7096,7141,7186,7231,7276,7321
		dc.w	7366,7411,7456,7501,7545,7590,7635,7679
		dc.w	7723,7768,7812,7856,7900,7944,7988,8032
		dc.w	8076,8119,8163,8207,8250,8293,8337,8380
		dc.w	8423,8466,8509,8552,8595,8638,8680,8723
		dc.w	8765,8808,8850,8892,8935,8977,9019,9061
		dc.w	9102,9144,9186,9227,9269,9310,9352,9393
		dc.w	9434,9475,9516,9557,9598,9638,9679,9720
		dc.w	9760,9800,9841,9881,9921,9961,10001,10040
		dc.w	10080,10120,10159,10198,10238,10277,10316,10355
		dc.w	10394,10433,10471,10510,10549,10587,10625,10663
		dc.w	10702,10740,10778,10815,10853,10891,10928,10966
		dc.w	11003,11040,11077,11114,11151,11188,11224,11261
		dc.w	11297,11334,11370,11406,11442,11478,11514,11550
		dc.w	11585,11621,11656,11691,11727,11762,11797,11831
		dc.w	11866,11901,11935,11970,12004,12038,12072,12106
		dc.w	12140,12173,12207,12240,12274,12307,12340,12373
		dc.w	12406,12439,12472,12504,12537,12569,12601,12633
		dc.w	12665,12697,12729,12760,12792,12823,12854,12885
		dc.w	12916,12947,12978,13008,13039,13069,13100,13130
		dc.w	13160,13190,13219,13249,13279,13308,13337,13366
		dc.w	13395,13424,13453,13482,13510,13538,13567,13595
		dc.w	13623,13651,13678,13706,13733,13761,13788,13815
		dc.w	13842,13869,13896,13922,13949,13975,14001,14027
		dc.w	14053,14079,14104,14130,14155,14181,14206,14231
		dc.w	14256,14280,14305,14329,14354,14378,14402,14426
		dc.w	14449,14473,14497,14520,14543,14566,14589,14612
		dc.w	14635,14657,14680,14702,14724,14746,14768,14789
		dc.w	14811,14832,14854,14875,14896,14917,14937,14958
		dc.w	14978,14999,15019,15039,15059,15078,15098,15118
		dc.w	15137,15156,15175,15194,15213,15231,15250,15268
		dc.w	15286,15304,15322,15340,15357,15375,15392,15409
		dc.w	15426,15443,15460,15476,15493,15509,15525,15541
		dc.w	15557,15573,15588,15604,15619,15634,15649,15664
		dc.w	15679,15693,15707,15722,15736,15750,15763,15777
		dc.w	15791,15804,15817,15830,15843,15856,15868,15881
		dc.w	15893,15905,15917,15929,15941,15952,15964,15975
		dc.w	15986,15997,16008,16018,16029,16039,16049,16059
		dc.w	16069,16079,16088,16098,16107,16116,16125,16134
		dc.w	16143,16151,16160,16168,16176,16184,16192,16199
		dc.w	16207,16214,16221,16228,16235,16242,16248,16255
		dc.w	16261,16267,16273,16279,16284,16290,16295,16300
		dc.w	16305,16310,16315,16319,16324,16328,16332,16336
		dc.w	16340,16343,16347,16350,16353,16356,16359,16362
		dc.w	16364,16367,16369,16371,16373,16375,16376,16378
		dc.w	16379,16380,16381,16382,16383,16383,16384,16384
		dc.w	16384

colors		dc.w	0,$336,$669,$CCF

GfxName		dc.b	'graphics.library',0
IntuiName	dc.b	'intuition.library',0
DOSName		dc.b	'dos.library',0
IconName	dc.b	'icon.library',0
ESName		dc.b	'EuroStars',0
ESPortName	dc.b	'EUROSTARS',0	; ARexx wants uppercase.  Sigh...


****************
* Error strings
*
SyntaxErr	dc.b	"Syntax error.",10,0
BadVal		dc.b	"Bad value.",10,0
ModeMissing	dc.b	"Must specify M, R, or S before XYZ.",10,0
MissingVal	dc.b	"Expecting more values.",10,0
ExtraVal	dc.b	"Unexpected extra value.",10,0
		dc.b	"$VER: "
HelpStr		dc.b	"ES 2.0 (18.10.92) -- EuroStars",10
		dc.b	"Written by Leo L. Schwab.",10,10
		dc.b	"Usage:",10
		dc.b	9,"ES [MRS][XYZ] <value> [<value> ... ]",10
		dc.b	9,"ES B",9,"; Run as background server",10
		dc.b	9,"ES Q",9,"; Quit (ARexx only)",10,10
		dc.b	"ARexx port name is EUROSTARS.",10,0


****************
* Static structure definitions.
*
		cnop	0,4

	;------	NewScreen
scr_def		dc.w	0,0
scrwide		dc.w	352
scrhigh		dc.w	230
		dc.w	2
		dc.b	0,1
		dc.w	0
		dc.w	CUSTOMSCREEN!SCREENQUIET
		dc.l	0
		dc.l	ESName
		dc.l	0
		dc.l	0

	;------	NewWindow
windef		dc.w	0,0
		dc.w	352,230
		dc.b	-1,-1
		dc.l	MOUSEBUTTONS
		dc.l	SMART_REFRESH!BACKDROP!BORDERLESS!ACTIVATE!RMBTRAP
		dc.l	0
		dc.l	0
		dc.l	0
scrptr		dc.l	0
		dc.l	0
		dc.w	0,0,0,0
		dc.w	CUSTOMSCREEN

	;------	MessagePort
rexxport	dc.l	0,0		; LN_SUCC, LN_PRED
		dc.b	NT_MSGPORT	; LN_TYPE
		dc.b	0		; LN_PRI
		dc.l	ESPortName	; LN_NAME
		dc.b	PA_SIGNAL	; MP_FLAGS
mpsigbit	dc.b	-1		; MP_SIGBIT
mpsigtask	dc.l	0		; MP_SIGTASK
		ds.b	LH_SIZE		; MP_MSGLIST,LH_SIZE

	;------	Startup Message
startmsg	dc.l	0,0		; LN_SUCC, LN_PRED
		dc.b	NT_MESSAGE	; LN_TYPE
		dc.b	0		; LN_PRI
		dc.l	ESName		; LN_NAME
		dc.l	rexxport	; MN_REPLYPORT
		dc.w	MN_SIZE		; MN_LENGTH

	;------	Interrupt structure
BOFintrnode	dc.l	0,0		; LN_SUCC, LN_PRED
		dc.b	NT_INTERRUPT	; LN_TYPE
		dc.b	0		; LN_PRI
		dc.l	ESName		; LN_NAME
StarTask	dc.l	0		; IS_DATA
		dc.l	BOFintr		; IS_CODE


****************************************************************************
* Uninitialized data
*
		SECTION EuroStars,BSS
StartBSS:

MyCopList	ds.b	ucl_SIZEOF
TransformBuff	ds.w	NSTARS*3	; Written in YXZ order
PlaneOffsets	ds.w	NSTARS

EndBSS:
		END
