=============================================================================
TUNN3L Journaal
=============================================================================


Proloog :) 
~~~~~~~~~~
T U N N E L

Pixel = dot op het scherm
Texel = dot uit een texture

Tekenen  is  loop  die  via look-up-table voor elke pixel de
juiste  texel  zoekt. Simpele loop en dus lekker snel uit te
voeren.

      Look-Up-Table          Texture             Screen

	 +-----+             +-----+             +-----+
	 |     |             |     |             |     |
Pixel -> |     | -> Texel -> |     | -> Color -> |     |
	 |     |             |     |             |     |
	 +-----+             +-----+             +-----+

Maakt  gebruik  van  een  pre-calculated look-up-table. Deze
wordt als volgt berekend:

Op  het  scherm  projecteren we een cilinder. Deze heeft een
lengte  L en een straal R. Als je de cilinder in perspectief
bekijkt  dan lijkt het alsof de straal steeds kleiner wordt.
Als  we  dan  het  scherm  bekijken  dan  valt het op dat de
afstand  van  een  pixel  tot  het  midden  van  het  scherm
(CenterX,  CenterY)  gelijk is aan de straal. Bij pixels die
rond de border van het scherm liggen is de straal groter dan
van  pixels die dichterbij het centrum liggen. Dus liggen de
border-pixels  eigenlijk dichterbij dan de center pixels. En
dat is precies het effect dat we zochten, want dan lijkt het
alsof we de tunnel, ofwel de cilinder inkijken.

Nu  is  het zaak om de texture in deze cilinder te 'mappen'.
We hebben net gezien dat elke pixel in de cilinder ligt, dus
als we nu voor elke pixel de bijbehorende texel willen weten
kunnen we dan dmv. een tabel opzoeken.

Om  de  texture  'om'  te cilinder te mappen (en te wrappen)
doen  we het volgende. De texture wordt met de y-richting in
de  lengte  richting  gemapt  en  de  x-richting  wordt  dus
eigenlijk om de cilinder gevouwen. Plaatje!

De  omtrek  van  een cirkel is 2*pi radialen. Wij werken met
texels,  en als we in totaal 1 texture om de cilinder willen
vouwen,  dan  is de omtrek van de cilinder dus gelijk aan de
breedte  van de texture (bijv. 256 texels). Willen we 2 of 3
textures  naast elkaar mappen dan wordt de omtrek gelijk aan
2*256 of 3*256 texels. Meer kan natuurlijk ook!

De  x-coordinaat van de texel heet u en de y-coordinaat heet
v. Beide bepalen we met behulp van de straal. Deze straal is
te  bepalen door de afstand van de pixel tot het centrum van
het scherm uit te rekenen:

	dX = X - CenterX
	dY = Y - CenterY

	R = Sqrt(dX*dX + dY*dY)		// Phytagoras

Om  nu  U  te  kunnen bepalen moeten we de hoek weten die de
straal maakt. Hiervoor een stukje trig:

	tan (alpha) = dY / dX

	alpha = arctan (dY / dX)

Nu  weten we de hoek in radialen, maar we willen graag weten
welke  hoek  in  texels, ofwel welke u coordinaat we hebben.
Daarom rekenen we even om van radialen naar ons eigen graden
systeem  (2*pi  radialen  was  256  texels (of 2*256, 3*256,
etc). Dus alpha radialen zijn 256 / (2*pi) texels:

	u = alpha * 256 / (2*pi)

Opmerking:  als  we  meer  dan 1 texture naast elkaar willen
vouwen,  dan  moet  U  wel  wrappen. Maw. als de texture 256
texels  breed  is,  dan  mag u niet meer dan 256 worden. Dit
doen  we door u te 'anden' met 255. Dit is nodig, want als U
>  256,  bijvoorbeeld  356, dan hebben we ook V beinvloed. U
zit  dan namelijk op 100 texels naar rechts, en V is 1 texel
naar  beneden  verschoven.  In dat geval sluiten de textures
niet  goed op elkaar aan op het scherm en is de verschuiving
duidelijk zichtbaar als ze dichtbij komen.

Dat  is  dus de U coordinaat. Om V te berekenen gebruiken we
de lengte van de straal R. We zagen al dat de straal kleiner
werd  naarmate we verder in de tunnel terecht kwamen. Dus de
lengte  van de straal zorgt voor de V coordinaat. We zijn er
echter nog niet. De straal wordt kleiner als we verder in de
tunnel  komen,  maar om een perspectief te creeren moeten we
steeds  grotere  stappen  in  de  v-richting  doen, zodat de
textures  steeds  dichter  op elkaar komen te zitten. Daarom
moeten   we  niet  R  gebruiken,  maar  1/R  !  Deze  waarde
corrigeren we nog even...de minimale lengte van de tunnel is
256,  dan  past  er precies 1 texture in de tunnel. Maar dat
ziet  er natuurlijk niet uit, we hebben dan geen goed gevoel
van  perspectief. Daarom vermenigvuldigen we deze waarde met
32,  zodat  er  in  totaal  32 textures tegelijkertijd in de
tunnel   passen.   Dit   levert  een  mooi  perspectief  op.
Samenvattend:

	v = 32 * 256 / R

Opmerking:  V  moet  ook  wrappen,  mag  dus  niet > dan 256
worden,  omdat  we  dan  buiten de texture uit komen wat kan
resulteren in trash op het scherm. Dus ook V and 255.

U  en  V  zorgen nu samen voor een offset in de texture, die
met de volgende formule berekend kan worden:

	Offset = U + V * 256

Deze  offset schrijven we in de look-up-table. Note dat deze
offset  nooit  meer  dan $ffff kan worden, omdat U en V niet
meer  dan  255 kunnen zijn. De look-up-table bestaat dus uit
words (evenveel als het aantal pixels op het scherm).

Om  nu  de  tunnel  te tekenen loopen we door alle pixels en
kijken  in  de  tabel wat de bijbehorende texel is voor elke
pixel  (ofwel, we lezen de offset uit, en tellen die bij het
texture-adres op).

Door de tunnel scrollen kan ook. Dan tellen we nog een extra
waarde  bij  het  textureadres.  Als  deze scroll waarde een
veelvoud is van de breedte van de texture (256 dus) dan gaan
we  vooruit  (of als we de scroll aftrekken, achteruit). Als
scroll  niet  een veelvoud van 256 is dan draaien we ook nog
rond...  Note  dat  bij  het  scrollen  wel  de  offset (dus
offset=lut[pixel]+scrl)  geand  moet  worden met $FFFF zodat
deze niet buiten de texture valt ...


21 Dec 1996
===========

Projecteren van de walls
~~~~~~~~~~~~~~~~~~~~~~~~
De  diepte van de tunnel is bepaald door het aantal textures
dat  we  in  de tunnel willen hebben. Dat zijn er 32, dus de
diepte  is  gelijk  aan  32*TxtH texels. Onze texture is 256
texels  hoog,  dus  dat maakt de totale diepte van de tunnel
gelijk aan 32*256.

We  hebben nu dus 32*256 verschillende Z-waarden. Op elk van
deze z-waarden kan zich een wall bevinden. Om perspectief te
suggereren  tekenen  we walls die ver weg liggen (en die dus
een  grote  z-waarde hebben) verkleind en walls die dichtbij
liggen  tekenen  we  vergroot.  Om  de  nieuwe afmetingen te
bepalen gebruiken we de volgende formule:

	Size = TunnelD / Z

Dit  is  dus  een  3d->2d projectie. Als Z=TunnelD dan is de
grootte  van  de  wall gelijk aan 1, als Z=1, dan is de size
gelijk aan 32*256, dus we hebben perspectief nagebootst.

Het  klopt  nog  niet  helemaal,  want de walls zijn 128x128
bitmaps  en  de tunnelwand texture is 256x256, dus we moeten
Size  nog  met twee vermenigvuldigen. Daarom gebruiken we de
constante TunnelD2, deze is tweemaal zo groot als TunnelD.

Size  is zowel de hoogte als de breedte van de wall, deze is
dus  altijd  vierkant  (op  een  scherm  met 1:1 pixel ratio
natuurlijk  ;).  We  moeten  nu  alleen  nog  maar de X en Y
coordinaat berekenen. Deze zijn gelijk aan:

	X = CenterX - Size/2
	Y = CenterY - Size/2

Omdat  walls  met hele kleine afmetingen nauwlijks zichtbaar
zijn,  hoeven  we  deze natuurlijk niet weer te geven op het
scherm.  We  berekenen  de  maximaal  zichtbare Z-coordinaat
ViewD met de volgende formule:

	ViewD = TunnelD2 / Minimale_Breedte

Note  dat we weer TunnelD2 gebruiken omdat de walls 2x zulke
kleine  afmetingen  hebben  dan  de textures. ViewD is nu de
maximale  Z-coordinaat.  De  minimale Z-coordinaat is gelijk
aan  1.  Dit  houdt  in dat we een LookUpTable voor de walls
kunnen  maken:  voor  elke  Z coordinaat houden we bij welke
Size,  X en Y waarden er mogelijk zijn. Dit scheelt weer een
hele hoop rekentijd!


Tekenen van de "racebaan"
~~~~~~~~~~~~~~~~~~~~~~~~~
De walls zijn opgeslagen in een array, Wall[]. Van elke wall
wordt  zijn Z coordinaat opgeslagen en van welk type hij is.
Een  racebaan  is  dus  eigenlijk  een lange rechte lijn met
daarop een aantal walls. Het type van een wall bepaald welke
texture  er gebruikt wordt en hoe de "hole" eruit ziet zodat
we op collisions kunnen checken.

De  eenvoudigste  aanpak  om de walls te tekenen zou zijn om
deze array van achter naar voren te scannen en elke wall die
in  het  bereik  [PlayerX, PlayerX + ViewD] valt te tekenen.
Maar  dat  is  langzaam,  daarom  gebruiken  we een slimmere
manier,  huhuh.  De  array  is  namelijk  al  gesorteerd  op
z-waarde. Walls aan het begin van de array hebben een lage Z
en arrays aan het einde een hoge Z-coordinaat.

Stel  dat  er 4 muren in het zichtbare gebied liggen, als we
dan door de baan gaan scrollen, dan verdwijnt op een gegeven
moment  de  voorste  muur  en  komt  er soms een muur aan de
achterkant bij. Op deze manier:

Stap 1		Stap 2		Stap 3

|   |          	|   |           ##### Muur 4
|   |          	|   |           |   |
##### Muur 3   	|   |           |   |
|   |          	##### Muur 3    |   |
|   |          	|   |           ##### Muur 3
##### Muur 2   	|   |           |   |
|   |          	##### Muur 2    |   |
##### Muur 1   	|   |           ##### Muur 2
|   |          	##### Muur 1    |   |
##### Muur 0   	|   |		##### Muur 1


Als  we  nu  bijhouden  welke muur het dichtste bij ons ligt
(FirstWall)  en  welke  het  verste weg ligt (LastWall), dan
kunnen  we  de  Wall[]  array  simpelweg  van  LastWall  tot
FirstWall en scannen en deze muren 1 voor 1 tekenen. In stap
1  van  de  plaatjes  hierboven is FirstWall gelijk aan 0 en
LastWall is 3. Even later is Muur 0 niet meer zichtbaar, dus
FirstWall moet verhoogd worden tot 1. Er is geen nieuwe muur
bijgekomen  dus LastWall verandert niet. In de volgende stap
is Muur 1 nog steeds zichtbaar, dus FirstWall blijft gelijk,
maar er is een nieuwe muur bijgekomen (Muur 4), dus LastWall
wordt verhoogd.

Als  alle  muren  op zijn moet LastWall natuurlijk niet meer
verhoogd worden. Als de laatste muur niet meer zichtbaar is,
dan  wordt  FirstWall  nog  wel  verhoogd.  Op dat moment is
FirstWall  groter  als  LastWall, dan weten we dat het level
klaar  is.  Dan  kunnen we het vliegtuigje nog even af laten
remmen en een animatie laten zien ofzo.


Opmerking
~~~~~~~~~
Dit   algoritme   voor  het  weergeven  van  de  walls  moet
natuurlijk  wel  geini-  tialiseerd  worden,  want  er wordt
nergens  bij  gehouden  hoeveel  walls er initieel zichtbaar
zijn.  FirstWall  is  eenvoudig,  die  is gelijk aan 0, maar
LastWall  is  onbekend.  Hier  is echter geen aparte routine
voor  nodig,  want  elke  keer  dat  DrawWalls()  aan  wordt
geroepen  wordt  LastWall  met 1 verhoogd, net zolang totdat
alle  zichtbare  walls  op  het  scherm  staan.  Dus  als er
initieel  vier  walls zichtbaar zijn, dan staan deze na vier
keer aanroepen van DrawWalls() op het scherm en is de initie
compleet.

Maar  we  hoeven natuurlijk niet voor dat de hoofdlus begint
vier  of  meer  keer  DrawWalls() aan te roepen, dit gebeurt
automatisch  al  in  de hoofdlus. Het enige nadeel is dat de
oplettende  kijker  ziet dat er in de eerste seconde van het
spel  steeds  een  wall  bijkomt.  Maar  dit is eenvoudig te
verhelpen  door  het scherm tegelijkertijd in te laten faden
terwijl   het   vliegtuigje  langzaam  accellereerd  tot  de
beginsnelheid. Als het scherm klaar is met faden dan kan het
spel beginnen.

Aan het begin van een level kunnen FirstWall en LastWall dus
gewoon op 0 geinitialiseerd worden.


Opmerking
~~~~~~~~~
Het  algoritme  voor  het  tekenen van de walls werkt alleen
maar  als  de positie van de speler _toe_neemt, dus als zijn
Z-coordinaat  verhoogd.  Met  het  achteruit bewegen is geen
rekening  gehouden,  en terecht want dat is helemaal niet de
bedoeling  van  het  spel  ;)  De  snelheid (Player.Spd) kan
overigens  niet  gelijk  aan 0 worden, maar is minimaal 256.
Dit  zodat  je niet stil kan gaan staan, dus ook als je niks
doet  is  het  level  na  verloop van tijd afgelopen (of wat
waarschijnlijker  is, je bent dood omdat je tegen alle muren
aan bent geknald...).


Opmerking
~~~~~~~~~
Als er op een stuk baan helemaal geen muur zichtbaar is, dan
is er geen probleem. In dat geval is FirstWall namelijk meer
dan  LastWall,  en  wordt  de loop niet doorlopen. Alleen de
Speler/FirstWall  test  moet  dan  geskipt  worden,  want de
FirstWall mag dan niet getekend worden natuurlijk...


Probleem
~~~~~~~~
Door  BufferW,  H  en  S  te veranderen is de grootte van de
viewport aan te passen. Maar het renderen van de graphics is
niet afhankelijk van de grootte van de viewport, dus je ziet
minder  als  je  de viewport verkleind. Dit betekend dat het
lijkt  alsof  de  tunnel minder diep is, je sneller een muur
bereikt, enz.

Dit gebeurt er:       Maar het volgende zou moeten gebeuren:

 +-------+			+-------+
 |.#####.|      +-----+         | ##### |      +-----+
 |##''`##|      |#''`#|         |##''`##|      |.###.|
 |#:   :#| ---> |:   :|         |#:   :#| ---> |#: :#|
 |##...##|      |#...#|         |##...##|      |`###'|
 |`#####'|      +-----+         | ##### |      +-----+
 +-------+                      +-------+

Als   het  uiteindelijke  spel  maar  gebruik  maakt  van  1
mogelijke  beeldscherm  grootte,  dan  is  dit probleem niet
interessant,  maar  als  er meerdere groottes mogelijk zijn,
dan moet hier wel rekening mee worden gehouden!


Opmerking
~~~~~~~~~
Bij  een gewoon tunneleffect is het mogelijk om de tunnel te
laten  draaien,  door de Scroll-offset niet met een veelvoud
van 256 te veranderen, maar bijvoorbeeld met 100. Maar nu er
walls  in  de  tunnel  zitten kan dit niet, de Scroll-offset
rekent  namelijk  in  texels, maar de Z-waarde voor de walls
wordt  berekend  in  regels. Als Scroll dan met bijvoorbeeld
100  wordt  verhoogd,  dan  is er nog geen regel afgelegd (1
regel  is  256 texels), dus Z wordt niet verhoogd en de wall
blijft stilstaan. Dit klopt dus niet.

Verder  is  het visueel ook niet juist als de tunnelwand wel
ronddraaid,  maar  de wall blijft stilstaan, dan zou de wall
eigenlijk  ook  om zijn Z-as moeten draaien, maar dat is nog
langzamer.

Conclusie: de tunnel niet laten draaien.


Opmerking
~~~~~~~~~
De  structure  van  de LookUpTable voor de Walls ziet er als
volgt uit:

struct TWallLut
{
  ULONG	S;	// Width and height of destination image
  ULONG	X;	// }_ TopLeft coordinates of
  ULONG Y;      // }  destination image
};

Hoewel  S,  X  en  Y  toch nooit meer een een WORD in beslag
nemen  zijn  ze als LONGS geimplementeerd. Dit komt omdat de
Scale()  routine  graag LONGS als input heeft. Het nadeel is
dat  de  tabel  wat  groter  wordt,  maar  nog altijd binnen
aanvaardbare  grenzen  (rond  de  20K). Er zit ook een klein
voordeeltje  aan  want op een 32 bits processor (680x0) gaat
het werken met LONGS altijd sneller dan het werken met WORDS
of BYTES. Vandaar.


Opmerking
~~~~~~~~~
Player.Spd   en   Player.Scroll  stellen  de  tunnel  scroll
snelheid   en   positie   voor   in   texels,  dus  niet  in
regels...Omdat ronddraaien niet mocht (zie andere opmerking)
moet  Player.Spd  _altijd_ een veelvoud van 256 zijn. Daarom
moeten  ook  MinSpd  en  MaxSpd  altijd een veelvoud van 256
zijn.  Anders zou de tunnel toch nog rond gaan draaien als 1
van deze snelheden bereikt wordt.


Wall Design
~~~~~~~~~~~
Na wat experimenteren met verschillende muurtypen blijkt dat
alleen deze muren voldoen:

 ##..   ..##    ####    ....    ..##                      #..#
###  . .  ###  ######  .    .  .  ###                    ##  ##
###  . .  ###  ######  .    .  .  ### etc. maar deze --> ##  ##
###  . .  ###  .    .  ######  ######      is niet       ##  ##
###  . .  ###  .    .  ######  ######      echt geschikt ##  ##
 ##..   ..##    ....    ####    ####                      #..#

Muren  met  alleen  een  gat  erin  zijn  nogal makkelijk om
doorheen te vliegen, omdat het gat het hele scherm vult, dus
je  kan nergens tegen op knallen ;) Andere, slecht onworpen,
muren  hebben  wel  een gat maar het is onmogelijk om er dan
door  heen  te  vliegen. Maar met de bovenstaande muren moet
het  toch  wel  mogelijk  zijn om een paar heftige levels te
bouwen.

Trouwens,  teveel muren kost te veel geheugen. 16Kb per wall
bitmap,  als  ik  byte-per-pixel  chunky  gebruik. Drie keer
zoveel voor copper-chunky :(


Wall Design
~~~~~~~~~~~
Volgens  mij is het scaling algoritme wel goed en als ik het
met andere algoritmes, programma's etc. vergelijk is er geen
verschil  in  kwaliteit,  maar  toch  vind  ik  dat het maar
schokkerig  gaat.  Maar  ook in DPaintIV gaat het zo. Om dit
effect  te  verminderen  moet  een  wall  zonder  al te veel
contrast  worden  getekend,  en als er contrast in moet, dan
niet met al te veel rechte lijnen ;)

Randen  van  de  walls  zijn  rond  en  komen  daarom nog al
"jagged"  uit  de  scale  routine, vooral als de zoom-factor
groot  is. Dit effect is te verminderen door de kleur van de
_randen_  niet te veel te laten verschillen van de kleur van
de   tunnelwand-texture.   En   natuurlijk  de  wall  bitmap
anti-aliasen.



23 Dec 1996
===========

Tekenen van speler
~~~~~~~~~~~~~~~~~~
De speler wordt een eindje de tunnel ingetekend, zeg maar op
een  Z  van  200.  Dus  als Player.Z gelijk is aan 1000, dan
wordt  de  speler  als  het  ware  op een Z-positie van 1200
getekend.

Dit  heeft  wel consequenties voor het tekenen van de walls,
want   nu   kan  het  voorkomen  dat  de  speler  geheel  of
gedeeltelijk   achter  een  muur  zit,  bijv.  als  deze  op
Z-positie  1100 staat. Dan moet dus eerst de speler getekend
worden en daarna deze muur.

Gelukkig kan alleen de FirstWall de speler "obscuren" dus we
kunnen  dit  probleempje  met een simpele test oplossen. Als
RelZ  van  FirstWall > 200 dan ligt de muur achter de speler
en moet deze muur eerst getekend worden en daarna de speler.
Als  RelZ echter < 200 dan moet eerst de speler en daarna de
wall  op  het scherm worden gezet. Voor het getal 200 is een
constante  gemaakt  met  de naam RealZ (omdat het de echte Z
van  de  speler is). Note dat RealZ, net als RelZ _relatief_
is t.o.v. de echte Z-coordinaten !

Maar  als  FirstWall  nu  nog  niet  zichtbaar  is  (dit kan
gebeuren  op  een  stuk track waarop helemaal nog geen walls
voorkomen,  FW is nu meer dan LW, maar FW is niet zichtbaar)
gaat  dit  niet op. We mogen dan geen walls tekenen, dus dat
stuk   skippen  we.  De  speler  moet  wel  getekend  worden
natuurlijk ;)


Speler graphics
~~~~~~~~~~~~~~~
Voor  het  schip  moeten een aantal verschillende aanzichten
gerenderd worden. Zodat het perspectief ook echt goed lijkt.

Voor  de  schaduw  moeten  een  redelijk groot aantal frames
voorhanden  zijn.  Deze moet namelijk met de tunnel meegaan,
want   een   schaduw   wordt   immers  altijd  op  de  grond
geprojecteerd.


Collision Checks
~~~~~~~~~~~~~~~~
We   moeten  alleen  checken  op  een  collision  tussen  de
FirstWall  en de speler. Dit moet gebeuren op het moment dat
de  Z-posities  van  beide gelijk zijn. Dit is op het moment
dat   RelZ  gelijk  is  aan  RealZ.  Als  dan  de  "bounding
rectangle"  van  het  schip _niet_ _geheel_ in de hole ligt,
dan zijn we tegen de muur aangeknald...

Natuurlijk  is  het niet altijd mogelijk om precies op RealZ
te  testen,  dus  daarom testen we in een bereik rond RealZ,
maar  we mogen slechts 1 keer op een collision checken, niet
meerdere  frames  achter elkaar. Het bereik waarin we testen
moet  dus afhangen van de snelheid waarmee de speler door de
tunnel scrollt.


|     |			Voorwaarde 1: RelZ <= RealZ
|     |
------- RealZ           Voorwaarde 2: RelZ >= (RealZ - Player.ZSpd/256)
|     |
|     |                 Aan beide voorwaarden moet worden voldaan !
####### RelZ
|     |


De  eerste  voorwaarde zorgt ervoor dat we niet gaan checken
voordat   de   speler  de  muur  bereikt  heeft.  De  tweede
voorwaarde  zorgt  ervoor  dat  er maar in 1 frame gechecked
wordt.  Aan  deze  voorwaarde wordt namelijk maar in 1 frame
voldaan,  en  dat  is  de  frame waarin RealZ net gepasseerd
is...  In  de  volgende  frame is RelZ namelijk verlaagd met
ZSpd waardoor deze altijd lager is dan (RealZ - ZSpd/256).

Note dat Player.ZSpd door 256 gedeeld moet worden, want deze
snelheid  is normaal uitgedrukt in texels, maar we moeten de
snelheid  in  regels  hebben.  Deze  snelheid mag nooit meer
worden  dan RealZ. Op dit moment is ZSpd maximaal 25600, dus
ZSpd/256 = 100, en dit is minder dan RealZ (200). Voorbeeld:

	RealZ = 200
	ZSpd  = 100 (maximaal)
	RelZ  = 250

	Frame 0:
	RelZ = 250 - 100 = 150

	Voorwaarde 1: 150 <= 200	 	OK  	\_ OK
	Voorwaarde 2: 150 >= (200 - 100) 	OK      /

	Frame 1:
	RelZ = 150 - 100 = 50

	Voorwaarde 1: 50 <= 200			OK	\_ NOK
	Voorwaarde 2: 50 >= (200-100)		NOK     /


Als  de  speler  afremt, dan verminderd ZSpd, maar dat heeft
geen invloed op deze voorwaarden. Voorbeeld:

	RealZ = 200
	RelZ  = 250

	Frame 0:
	ZSpd = 100
	RelZ = 250 - 100 = 150

	Voorwaarde 1: 150 <= 200		OK 	\_ OK
	Voorwaarde 2: 150 >= (200 - 100)	OK	/

	Frame 1:
	ZSpd = 10
	RelZ = 150 - 10 = 140

	Voorwaarde 1: 140 <= 200		OK	\_ NOK
	Voorwaarde 2: 140 >= (200 - 10)         NOK	/

Dus  dit  klopt  ook.  Er wordt maar 1 keer op een collision
gechecked.


Wall Design
~~~~~~~~~~~
Het Wall Design-verhaal van 21 Dec gaat niet meer op ! Omdat
de  speler  nu  een  stuk  vooruit  geschoven is, is het wel
degelijk  mogelijk  om  "rare"  gaten  in  muren te stoppen,
zonder dat de speler er altijd doorheen kan vliegen. Nu vult
z'on  gat  het  hele  scherm immers nog niet op, want zo ver
zijn we nog niet ingezoomed. Het kan dus wel, joepie !


Holes
~~~~~
De   volgende   walls   hebben  allemaal  heel  erg  simpele
coordinaten  voor  de holes, deze veranderen namelijk nooit,
ze  zijn  voor  alle  Z-posities  gelijk.  Dat komt omdat ze
precies een kwart, of de helft van het scherm omvatten.


 ##..   	 ####   	 ..##
###  . 		######  	.  ###
###  . 		######  	.  ###
###  . 		.    .  	######
###  . 		.    .  	######
 ##..  	 	 ....    	 ####

(80,0) -        (0,50) -        (0,0)-
(159,99)        (159,99)        (79,49)


Alleen  de  coordinaten voor afwijkende holes zijn rottiger.
Maar  het  gat  bij bijv. Start en Finish muren is zo groot,
dat  de  hole het hele scherm omvat. Daar kan je dus nergens
tegen op knallen.

Note:  deze  hole coordinaten zijn de coordinaten die gelden
op  het  moment dat er op een collision wordt gechecked (dus
de  frame waarin RealZ over- schreden wordt, zie hierboven).
Op alle andere momenten zijn de coordinaten van de hole niet
interessant.


Speler Bewegen
~~~~~~~~~~~~~~
Omdat de tunnel rond is, is het gebied waarin de speler zich
kan  bewegen  beperkt tot een cirkel (en niet een rechthoek,
zoals  dat  normaal  is). Hier moet dus op gechecked worden.
Dit kan mbv. de volgende formule:

	 2     2         2
	X  +  Y  <=  Rmax

Aan  deze  voorwaarde moet altijd voldaan zijn. Rmax is hier
gelijk  aan  (BufferH-SpelerShapeHoogte)/2.  X is gelijk aan
(Player.X  - CenterX) en Y aan (Player.Y - CenterY). Als X*X
+  Y*Y  groter is dan Rmax*Rmax dan wordt de beweging teniet
gedaan,  deze  is  immers  niet toegestaan. Anders wordt het
schip verplaatst.


GamePlay Idee
~~~~~~~~~~~~~
Als  je tegen een muur opgeknalt bent, dan zit speler achter
de  muur, daarom opeens de ZSpd omdraaien, dan lijkt het net
of  we  terugknallen en de speler komt weer aan de zichtbare
kant van de muur waardoor we de ontploffing kunnen zien ;)

Dan  langzaam afremmen totdat we stilstaan. Nu mogen we niet
meer  op collisions checken enzo natuurlijk...Speler kan ook
niet meer bewegen etc.


DoWalls
~~~~~~~
Bij  FirstWall/Speler  gedeelte:  IF  (RelZ  <  ViewD)  moet
UNSIGNED test zijn.




************************ DEVELOPMENT VERDER OP AMIGA ************************



24 Dec 1996
===========

AMIGA - BlitterScreen
~~~~~~~~~~~~~~~~~~~~~
Blitterscreen  routines  aan de praat gekregen onder ASMOne.
Deze  routine maken gebruik van een chunky buffer (ch0), het
scherm  (scr0),  een pass-buffer (buf2), een scramble-buffer
(sblbuf)  en een spritebuffer (sprbuf). Bij elkaar goed voor
meer  dan  400  K  aan  chipmem  :(  De  blitterqueue (ofwel
blitlist) komt in fast ram en is iets van 2K groot.

Met de routine mkbltscr wordt een copperlist gebouwd waarmee
de scherm hardware wordt ingesteld. Deze routine roept mk2xY
aan,  die  de  copperlist  verder uitbreidt door er gegevens
over  de  sprites in te schrijven (mbv mspr). mkbltscr is de
enige routine die door de gebruiker aangepast hoeft te (mag)
worden als er iets extra's in de copperlist moet staan.

Tijdens  de  initie  van  ons programma roepen we dan ook de
routine  c2bs  aan, welke het 1 en ander voorbereid. Tijdens
de hoofdlus doen we het volgende om te c2p'en:

	wachten tot bltbsy gelijk is aan 0
	wait vbl (eventueel)

	bltpc gelijk maken aan het adres van de bltlist
	bltbsy op 1 zetten
	BLIT3 in INTREQ zetten -> de blitter wordt nu gestart

Nu  is  de  processor  vrij om een nieuwe frame te berekenen
terwijl de blitter aan het c2p'en is.

BlitterScreen  maakt  gebruik van een interrupt handler voor
interrupt  3  ($6c) nl. int3, deze zorgt voor VBlank, Copper
en Blitter.


Palette: kleur 0 moet zwart zijn, anders zie je flikkeringen
in de scherm border.


DrawTunnel
~~~~~~~~~~
Ik had gedacht dat de innerloop zoiets zou zijn:

	move.w (ax)+,d0			; Lees Offset
	add.w d1,d0			; Offset += Scroll
	move.b (a0,d0.w),(ay)+		; Lees en put texel

Maar  dat gaat niet goed, d0.w wordt namelijk ge-SIGN-extend
naar  een long, en is dus _signed_. Omdat de texture 256x256
pixels  is,  hebben  we een offset nodig die tot 65535 gaat.
Maar  een  signed word gaat maar tot 32767, en dan wordt hij
negatief.  Opgelost door d0.l te gebruiken. Omdat we "add.w"
doen  wordt  d0 toch nooit meer dan 65535. Maar we moeten d0
aan  het begin van de routine wel helemaal leeg maken (moveq
#0,d0)  omdat  we  niet weten wat er in het most-significant
word staat.


25 Dec 1996 
===========

Blitterscreen met double buffering
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Om 2 redenen moeten we double bufferen:
1. we mogen niet in dezelfde chunky buffer renderen waarin c2bs
   aan het ploegen is. Dit levert lelijke effecten op bovenin 
   het scherm.

2. als we het scherm laten zien waarnaar c2bs aan het
   converteren is, dan treden er flikkeringen op. Dit is het
   "standaard" probleem bij elk type graphics ivm.
   electronenstraal, etc.

Benodigde buffers:
o 2 chunky buffers
o 2 schermen, om het flikkeren tegen te gaan.
o 2 copperlists: om snel tussen de schermen te wisselen,
  laten we gewoon elke frame een nieuwe copperlist zien.
o 2 blitterlists: als we c2bs aan zouden roepen in de
  mainloop, dan is er maar 1 blitterlist nodig, want deze
  wordt steeds opnieuw berekend. Maar dit kost tijd, dus we
  pre-calculaten 2 lists (eentje voor elke chunky-buffer + 
  scherm) voordat we de mainloop ingaan. Dan wisselen we
  steeds van list in de mainloop (het c2p proces wordt gestart
  door bltpc aan het begin van de list te zetten, bltbsy op 1
  te zetten en de blitter te starten).
o de andere buffers (sprite, scramble, pass) hoeven niet
  dubbel!

De  chunkybuffers  heten  rendch  en  dispch. De copperlists
heten  rendcop en dispcop en de blitterlists heten rendbl en
dispbl.  Dispxx is de zichtbare (hier mag niks mee gebeuren,
deze  is  in  de vorige frame gerenderd) en Rendxx is degene
waarin/mee gerenderd wordt (deze is niet zichtbaar).

Nu moeten we steeds in de mainloop het volgende doen:

	Copperlist dispcop actief maken
	Rendcop en dispcop wisselen
	Rendch  en dispch  wisselen
        Rendbl	en dispbl  wisselen

	C2P aan het werk zetten met dispbl !
	Renderen in rendch

Dus we renderen iets in rendch, deze wordt de volgende frame
omgezet  naar  een  planar  scherm  en  pas het frame daarop
gedisplayed. We lopen dus steeds 2 frames voor met wat er op
het scherm gebeurd (dus het scherm loopt 2 frames achter).


Tunnel Shading
~~~~~~~~~~~~~~
Het  zou  veel  mooier  zijn,  vooral  voor  het  gevoel van
perspectief,  als de tunnel geschaduwd werd. Hoe verder, hoe
donkerder.

We  kunnen  in  de TxtLut een shading factor meegeven (extra
byte  (of  word  voor  longword  alignen ofzo)). Als we deze
4-bits  maken  dan zijn er 16 verschillende shading factors.
Dan  geven we de texture de beschikking over 128 kleuren uit
het palette. Met deze 128 kleuren moet alles gebeuren...

Dan wordt het zoiets denk ik:

64 kleuren helder
32 kleuren minder helder
16 kleuren nog minder helder
8 kleuren nog minder helder dan daarnet
4 kleuren nog nog nog minder helder
4 kleuren bijna zwart

Programmatje  schrijven  dat 64 kleuren van de texture neemt
en er dan automatisch de andere 64 bij berekend...ofzo.

Maar  uh,  walls moeten dan eigenlijk ook geshaded worden...
en het is al zo langzaam :(


26 Dec 1996
===========

Blitterscreen
~~~~~~~~~~~~~
Kleur 0 = borderkleur van scherm, deze moet zwart zijn.
Kleur 1 = kleur van spritemask, deze moet donkergrijs zijn,
          dat geeft het beste effect.

ScaleWall
~~~~~~~~~
De  scale  routine  is  geimplementeerd.  Er is een speciale
optimalisatie  gemaakt  omdat  we  altijd  met 128x128 walls
werken.  Deze  SourceW  en  H  liggen  dus vast. Fixed point
precisie  is  teruggebracht  van  8 naar 16, omdat we anders
divul  en  mulu.l nodig hadden ipv. dan divu.w en mulu.w. En
dat willen we niet natuurlijk.

Als  ik nu bij start + end van level het scheepje in/uit wil
laten  zoomen,  dan  kan  ik  deze  routine  niet  gebruiken
natuurlijk,  maar  zal  er  een andere scale-routine, of een
zooitje pre-calculated shapes aan te pas moeten komen !!!


Opstart-Idee
~~~~~~~~~~~~
o Opstart-routines moeten cache aanzetten.
o Opstart-routines moeten display naar PAL forcen (via GFX-lib
  call, of evt. door lekkere poke in een hardware register:
  move.w #32,$dff1dc).
o DOS-Shell openen en weergeven wat er gebeurd + evt. foutmeldingen.



27 Dec 1996
===========

ScaleWall
~~~~~~~~~
Nog  meer  geoptimaliseerd  voor  128x128 walls: je geeft nu
alleen SourcePtr, BufferPtr, BufferX/Y en Size op. We scalen
toch  altijd  evenveel  in  de  X- als in de Y-richting. Dit
scheelt  weer een divu bij het berekenen van de steps (YStep
is nu evenveel als XStep).


DoWalls
~~~~~~~
De  Walls-tabel  bevat  de  gegevens  over alle walls, welke
Z-coordinaat  ze  hebben en van welk type dat ze zijn. In de
C-versie  van TUNN3L werd dit dmv. een struct gedaan, dat is
netjes  in  een hogere programmeertaal ;) maar in asm is het
tamelijk  onhandig. Daarom is deze array opgesplitst in twee
verschillende arrays: Wall_Z en Wall_Type.

Ook  de  WallLut  is  op deze manier gesplitst in WallLut_S,
WallLut_X  en  WallLut_Y.  Ook  andere  tabellen die hiervan
kunnen profiteren worden gesplitst.

Nadeel van splitsen: kost vrij veel adres registers. Kan wel
met  1  adresregister,  als  je  weet hoever de tabellen van
elkaar  vandaan  liggen  in  het geheugen. Dan is Ax pointer
naar 1e tabel, en Tabel2(Ax) pointer naar 2e, etc...

De  Level  array  hoeft  niet  perse gesplist. Op dit moment
wordt  er  toch  nog niets uit deze tabel gelezen...maar wie
weet   wat  er  in  de  toekomst  nog  allemaal  gebeurd  :)
Waarschijnlijk  wordt  Wall_Z  en  Wall_Type  dan  toch weer
anders  omdat het dan 2 dimensionale arrays worden: voor elk
level  zijn deze tabellen namelijk anders. Dit zouden we dan
op  kunnen  lossen door voor beide arrays een pointer bij te
houden  die na elk level verhoogd wordt (met NumWalls ofzo).
Iets dergelijks...


Omzetten PC -> Amiga
~~~~~~~~~~~~~~~~~~~~
De  LookUpTables e.d. worden op de PC uitgerekend. Om ze dan
om  te  zetten  moet  ik de byte-volgorde omdraaien. Dan wel
opletten  met  wat voor data we te doen hebben. De bytes uit
een WORD omdraaien werkt niet goed met LONG-data !!!


28 Dec 1996
===========

Speedup
~~~~~~~
Bij  een aantal wall-typen wordt er gewoon een heleboel voor
niks  gescaled omdat alle pixels transparant zijn, en dat is
zonde van de processor tijd.

Bij deze walls

 ##..    ###    ..##    ...
###  .  #####  .  ###  .   .
###  .  .   .  .  ###  #####
 ##..    ...    ..##    ###

kan  steeds een heel stuk over worden geslagen. Dit betekent
dat  deze  walls  ongeveer  twee  keer zo snel kunnen worden
gescaled. Een wall als:

 ..##
.  ###
######
 ####

kan niet sneller worden getekend.

Daarom   zou   de  ScaleWall  routine  nog  een  paar  extra
parameters  mee  kunnen  krijgen.  Of  we  geven  gewoon het
muurtype  mee  als  parameter,  zodat  ScaleWall  hier  zelf
rekening mee houdt. Het zijn per slot van rekening maar vier
muren die afwijken...


DoWalls
~~~~~~~
Als  FirstWall  >  LastWall  dan  kan  de lus niet doorlopen
worden,  er  zijn immers geen walls om te laten zien. Dus we
gaan  naar het gedeelte dat op FirstWall en SpelerZ test. Op
dat  moment  is d0 nog gelijk aan LastWall, dus dus RelZ die
berekend  wordt  is  de  RelZ  van  LastWall,  niet  die van
FirstWall!  En  LastWall ligt "achter" de speler, dus deze Z
is  altijd  minder  dan  RealZ...dus  dat  stuk  code  wordt
uitgevoerd  (eerst speler tekenen, dan muur scalen), maar ik
ben  benieuwd  met  welke  waarden  de  scale  dan uit wordt
gevoerd. In ieder geval is dat niet juist.

Dus  we  testen of RelZ < ViewD en dan gaan we verder met de
bovenstaande  testen. Maar deze test moet UNSIGNED gebeuren.
Als  RelZ dan negatief is (zoals boven geschetst) dan is aan
deze  voorwaarde  niet  voldaan  en  wordt  alleen de speler
getekend. Er worden dan geen muren gescaled, en zo hoort het
ook !


Het grote arrays splitsen verhaal
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
WallType is nu ook gesplitst, in:

	Type_Bitmap
	Type_Hole

Een  entry  in  Type_Bitmap  is  nu 4 bytes groot (pointer).
Entries in Type_Hole bestaan uit 4 words, en zijn dus totaal
8 bytes groot. Dit stelt ons in staat om snel door de arrays
heen te steppen (ax,dx.w*4) en (ax,dx.w*8).

De elementen van een entry van Type_Hole worden aangesproken
met: Type_HoleX1, Type_HoleY1, Type_HoleX2 en Type_HoleY2.



31 Dec 1996
===========

Palette
~~~~~~~
De kleuren in het game-palette zijn als volgt verdeelt:
0 = borderkleur (zwart) -> niet gebruiken voor graphics
1 = spritemask (dgrijs) -> niet gebruiken voor graphics
2 = wit		}
3 = grijs	}- letters, cijfers, leestekens
4 = zwart	}
5..31 		reserved
32..63		speler ruimteschip
64..127		tunnelwall texture
128..255	walls


Tekst en nummers
~~~~~~~~~~~~~~~~
Om  tekst  op het chunkyscherm te schrijven is er de routine
WriteStr.  Parameters  zijn  pointer  naar (null-terminated)
string en de offset in de chunky buffer.

Om  een  digit (0..9) op het chunkyscherm te schrijven is er
de  routine  WriteDigit.  Parameters  zijn  de chunky buffer
offset en het getal.


Kleine aanpassingen
~~~~~~~~~~~~~~~~~~~
MinSpd  =  0,  ipv.  256. Het langzaam inzoomen van de walls
schokt  namelijk  als  de  pest.  Als MinSpd=256 dan beweegt
speler  zich zo langzaam mogelijk voort, dus is het schokken
het  duidelijkst zichtbaar. Dat is niet mooi natuurlijk, dus
daarom is MinSpd gelijk aan 0.

Note:  dit  heft  gelijk  ook  de vertraging op als een muur
dichterbij  komt  (dus  verder inzoomed). Tenminste voor het
gevoel van de speler dan...

Als  speler  nu stilstaat dan is er geen schokken uiteraard,
maar  het  geeft  dan  een beetje een _te_ statisch plaatje.
Beter  zou  zijn  om  de  speler  omhoog  en omlaag te laten
bewegen (random?) net alsof hij kunstmatig zweeft...


Speler gfx
~~~~~~~~~~
Speler  schip  moet  in  een aantal verschillende aanzichten
voorkomen.  1  kleur  moet de kleur van de engine zijn. Deze
moet oplichten zolang speler de fire-knop ingedrukt houdt en
uitdoven als speler de fire-knop loslaat !!!


1 Jan 1996 (<- hmm, 1997)
==========

Thought of the day
~~~~~~~~~~~~~~~~~~
Als FirstWall > (NumWall-1) dan is het level afgelopen, maar
daarvoor  moet  de  FINISH-wall wel eerst verdwenen zijn van
het  toneel...  dus  we  moeten een stukje doorvliegen na de
finish om een level te voltooien.

Beter  zou  zijn  om  te checken: FirstWall = NumWall - 1 en
RelZ  van  FirstWall  <  RealZ.  Als  aan beide condities is
voldaan dan is het level voltooid.


Spel ontwerp
~~~~~~~~~~~~
Start  van  level:  speler  (middenonderin beeldscherm) komt
redelijk  snel  aanvliegen  en  remt  ook  redelijk snel af,
totdat  hij  vlak  voor de START-muur staat (deze muur staat
bij  alle  levels  op dezelfde z-positie). Tijdens dit alles
fade  het  beeld langzaam in (12 bit, tja het palette is ook
maar 12 bit ;).

Als  het  schip  gestopt  is,  dan  komt de volgende melding
midden in het scherm:

	LEVEL  x
	TIME   x:xx
	SHIPS  x

	PRESS FIRE   (<- knippert)

Na  het indrukken van fire begint de race; de tijd begint te
lopen.  Linksboven  in  het  scherm staat de tijd die je nog
over  hebt  voor  het level (telt dus af). Rechtsboven staat
het aantal ships dat je nog hebt (aantal crashes).

Als    de    speler   voorbij   de   FINISH-muur   is,   dan
accelereert/remt  het  schip  tot  een  bep.  vaste snelheid
bereikt  is  en vliegt het verder door de tunnel. Het scherm
fade  uit.  Een evt. mededeling blijft gewoon staan... (niet
alle kleuren faden dus).



2 Jan 1997
==========

LevelInit
~~~~~~~~~
Plyr_Z begint op Z=0, snelheid is 28*256. De START-wall moet
altijd op Z=512 staan. Dan komen we precies mooi uit. Plyr_X
is 80 en _Y is 74 zodat het schip precies tussen de tekst in
komt te staan.

Mijn  custom VBL-routine, MyVBLHandler toggelt een bit in de
variabele  "Toggle".  Dit  gebeurt  om de 20 vertical blanks
(mbv.  ToggleCnt). Hiermee is text knipperend weer te geven,
zoals de "PRESS FIRE" mededeling bij LevelInit.


Gameloop flags
~~~~~~~~~~~~~~
Er treedt altijd maar 1 verschillende status op, of de flags
zijn  Normal,  of LevelInit, of Collision, etc. Als de flags
Normal  zijn,  dan wordt de teller FlgTimer op 0 gezet (door
MyVBLHandler). Als de flags <> Normal zijn dan gaat FlgTimer
tellen.  Hiermee zijn dus delays te maken, bijvoorbeeld voor
de speler-ontploffing-animatie.

Note:  flags  mogen elkaar niet "surpassen". Als er een flag
gezet  is, dan kan deze niet door een andere van zijn plaats
gestoten  worden.  Dus als er een OutOfTime is, dan mag, als
de  speler tijdens het knipperen van de OutOfTime mededeling
toch  nog  over  de finish lijn gaat, Game_Flags niet gelijk
worden  aan  LevelDone.  Voor  de  stukken code die op zulke
dingen  testen  moet  altijd  gekeken  worden  of Game_Flags
gelijk is aan Normal. Is dat niet zo, dan moet het stuk code
worden overgeslagen.


04 Jan 1996 (<- het blijft wennen ;)
===========

SpeedUps
~~~~~~~~
DrawTunnel  schrijft  nu  LONGS  naar  de chunky buffer ipv.
BYTES  en  is  hierdoor ontzettend veel sneller geworden. Nu
vliegt  de  tunnel  lekker  smooth,  maar nog steeds geen 50
Hz...

ScaleWall  houdt  nu  rekening  met  speciale walls, nl. met
WT_Left,  WT_Right,  WT_Up  en  WT_Down.  Bij  deze walls is
namelijk  altijd  de  helft  van  de wall transparant en die
helft  wordt dus voor niks gescaled. ScaleWall zou dus bijna
2  maal  zo snel worden als we deze lege stukken over zouden
slaan. Dit gebeurt nu volgens het volgende algoritme:

CASE Walltype
  WT_Left:   ClipW /= 2
  WT_Right:  ClipW /= 2
             BufferX = CenterX
  WT_Up:     ClipH /= 2
  WT_Down:   ClipH /= 2
             BufferY = CenterY
ENDCASE

Het is nu wel nodig dat de textures veranderen, als volgt:

 ####    ######		Deze textures kunnen de helft 
######   ######     	kleiner en nemen dus nog maar
######    ####		8192 bytes aan geheugen in beslag.

WT_Up	 WT_Down
128*64   128*64

 ##..    ##...		Deze textures blijven even groot.
###  .   ###  .         Dit komt omdat in innerloop de
###  .   ###  .         waarde van SourceW "ingebakken" is
###  .   ###  .         en deze variabel maken kost teveel
 ##..    ##...          tijd. Nu kost het maar 8192 bytes ;)

WT_left  WT_Right
128*128  128*128

Ook heeft ScaleWall nu een extra parameter meegekregen om
het Walltype aan te geven.


Makkelijker gegevens accessen
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Bij  InitLevel  wordt  een pointer berekend naar het huidige
level,  Game_LevelPtr.  Als  we  nu  iets uit de Level array
willen  hebben  dan kunnen we gewoon deze pointer referencen
ipv. steeds de juiste offset berekenen mbv. mulu's enz. Stuk
sneller en eenvoudiger dus.

Zo  ook  Game_WallZ  en  Game_WallType. Deze pointen naar de
juiste  positie  binnen  de  Wall_Z  en  Wall_Type  array om
bovenstaande reden. Zij worden bij ook InitLevel berekend.


Dingetjes
~~~~~~~~~
Plyr_Level  begint  op  1 met tellen. Level 0 staat zo stom.
Dus  bij  berekenen  van pointers, etc. moeten we Plyr_Level
met 1 verminderen.


Hall of fame
~~~~~~~~~~~~
Voor  elke track (=level) wordt de beste tijd bijgehouden in
de array BestTimes. De tijd is hier ook in ticks (dus in een
long).  In  de  array BestNames staan de bijbehorende namen.
Als  je  een  betere  tijd hebt gescoord dan in de BestTimes
array  vermeld staat, dan krijg je een melding op het scherm
"BEST TRACK TIME" bij het finishen.

Bij  DoPlayer,  in  het LevelDone stuk wordt op deze records
getest.  Als  je  een nieuwe tijd hebt, dan gaat die melding
staan  flitsen.  Als we dan uit de GameLoop geexit zijn, dan
wordt  daar weer getest en de nieuwe tijd in BestTimes gezet
en  de  nieuwe  naam  in  BestNames. Dat gebeurt dus niet in
DoPlayer...


Intro
~~~~~
Introscherm  is  8 bitplanes diep. De eerste 6 planes worden
gebruikt voor een plaat, de achterste 2 planes voor text. De
laatste 2 planes zijn 512 pixels hoog, twee keer zo hoog dus
als de andere planes. In de onderste 256 regels schrijven we
tekst.  Dan  schuiven  we  deze  planes  via  een sinustabel
omhoog, zodat de text zichtbaar wordt. Daarna schuiven we de
planes  weer  naar beneden (zelfde tabel, in andere richting
gelezen).  Als  de  planes  weer terug in de beginstand zijn
kunnen we weer nieuwe tekst schrijven, etc...

Omdat  scr0 niet gebruikt wordt tijdens het intro, is alleen
maar in gebruik tijdens de game, kunnen we deze mooi voor de
laatste  2  planes gebruiken. We hebben 2*320*512/8 pixels =
40960  bytes  nodig.  Komt  precies  mooi  uit  want scr0 is
320*128 = 40960 bytes...

Voor  het inschuif warp-effect moeten we elke vertical blank
nieuwe   bitplane   pointers  in  de  copperlist  schrijven.
Gelukkig  kan dit tijdens de vertical blank en dus hoeven we
de copperlists niet te double bufferen.

De  copperlist  voor  het intro is in de source opgenomen en
wordt  dus  niet  gealloceerd  zoals de copperlists voor het
game-display.  De intro coplist is ook niet zo groot, alleen
zijn  er  vrij  veel palette-instructies, omdat we perse een
24-bits palette nodig hebben...

De warp-tabel wordt als volgt berekend:

FOR t=0 To 31
  offset =  Sin(t*2*Pi/128)*256 * 40   ; 40 bytes per line!
ENDFOR


6 Jan 1997
==========

Intro
~~~~~
De plaat is 6 bitplanes en bevat gewoon de kleuren 0..63. De
letters  op  de  laatste 2 bitplanes zijn gewoon getekend in
een  2  bitplanes  scherm  en hieruit gegrabt. Maar omdat ze
echter in planes 7 en 8 getekent worden moet het palette van
de  uiteindelijke  plaat  aangepast  worden.  Kleur 0 blijft
kleur    0,   deze   is   ook   voor   het   lettertype   de
achtergrondkleur.  Kleur  1  wordt  kleur  64, kleur 2 wordt
kleur  128 en kleur 3 van het font wordt kleur 192. Door het
palette  op  deze  manier aan te passen wordt het font in de
juiste kleuren gerenderd...

PutChr, PutStr
~~~~~~~~~~~~~~
PutChr  tekent  een karakter (ASCII 32..95) op de aangegeven
plaats  in het geheugen. PutStr tekent een hele string (mbv.
PutChr).


8 Jan 1997
==========

Schrijven van texten en nummers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Om  onderscheid  te  maken  tussen  de schrijf-routines voor
chunkybuffers   en  planar  schermen  zijn  ze  hernoemd  en
herschreven.   WriteXXX   is  komen  te  vervallen  (behalve
WriteTime). Nu zijn er:

PPutStr & PPutChr  -> voor planar scherm
CPutStr & CPutChr  -> voor chunky scherm

dmv. de macro's PPUTSTR, PPUTCHR, CPUTSTR en CPUTCHR zijn de
aanroepen  voor  deze  routines  wat versimpeld. Scheelt een
hele  hoop loze code tussendoor. Er is ook een macro CPUTNUM
om   snel   een  (enkel)  cijfer  op  het  chunky-scherm  te
schrijven. Dit is eigenlijk dezelfde macro als CPUTCHR, maar
er  wordt hier automatisch #48 bij het nummer opgeteld om te
converteren naar ASCII.

Dit  houdt  ook  in dat ook de chunky karakters een gedeelte
van  de  ASCII  set  bevatten (32..95). De bijbehorende file
heet ChunkyFont.CKY.


11 Jan 1997
===========

Keyboard input
~~~~~~~~~~~~~~
Gelukkig  had Fabio Bizetti een leuke keyboard input routine
in zijn Amiga Coder dinges. Na wat aanpassingen werkt GetKey
als volgt:

	Alle interrupts uit
	Lees een byte uit de serie-poort van CIA-A
	Zet serie-poort in output-mode
	Stel CIA-A timer A in op 64
	Zend handshake naar keyboard processor
	Wacht tot teller A underflowed (dus teruggeteld is
	naar 0)
	Zet serie-poort in invoer-mode
	Negeer de byte (keybrd. levert laag actief spul)
	Roteer de byte 1 positie naar rechts (keyb. levert
	6-5-4-3-2-1-0-7)
	
	IF key was released (bit 7 is 1, dus byte=negatief)
	  KeyState = KEY_NONE
 	  KeyRaw = byte
        ELSEIF byte is gelijk aan KeyRaw
          KeyState = KEY_REPEAT
	; Note: Hier KeyRaw *niet* veranderen natuurlijk!!!
        ELSE
          KeyRaw = byte
          KeyState = KEY_NEW
	ENDIF

De  raw  code  van  de  toets  komt  dus in KeyRaw te staan.
KeyState  geeft aan of de toets losgelaten is (KEY_NONE), de
toets  herhaald  wordt (KEY_REPEAT) of dat de toets voor het
eerst ingedrukt wordt (KEY_NEW).

Dit  is  belangrijk. Een track kan je stoppen door op ESC te
drukken.  Dan heeft KeyRaw nog de waarde voor ESC. Het Intro
controleert of ESC is ingedrukt, en leest KeyRaw. Vervolgens
stopt  het programma. Dat gaat dus niet goed. Het intro moet
eerst  testen  of  KeyState gelijk is aan KEY_NEW voordat op
het indrukken van ESC wordt getest.

Note: in de game-loop hoeft dit niet perse, als in het intro
op  ESC  is  gedrukt, dan komen we namelijk nooit meer in de
game-loop  ;)  MAAR toch moet het, er is namelijk ook nog de
pause  toets  P.  Die  moet  je  perse  2 keer achter elkaar
indrukken!

Als  de  toets wordt losgelaten, dan krijgt KeyRaw de waarde
van byte. Deze is nooit gelijk aan een toetswaarde, omdat nu
bit  7  gezet  is!  Het  is  wel  belangrijk dat KeyRaw hier
veranderd  wordt,  anders  bij  het  indrukken  van dezelfde
toets,  dan  is  deze nog steeds aan de oude toets, waardoor
KEY_REPEAT optreedt, terwijl de toets ondertussen al wel een
keer is losgelaten. Dat mag dus niet!


Pause
~~~~~
Tijdens  een level kan je het spel pauseren, maar alleen als
Game_Flags  gelijk  is  aan Flg_Normal. Tijdens een explosie
ofzo kan het niet...misschien nog wel erin maken, maar nu ff
niet.  Ik  weet  eigenlijk  ook  niet  waarvoor je pause zou
willen  gebruiken,  want  je  hebt al je tijd nodig voor het
vliegen van het level ;)


Palette
~~~~~~~
Het  gamepalette  is  nu  ook  24  bit,  zodat  SetPal12  nu
overbodig  is geworden...Nu hoeft ik dus ook alleen maar een
24-bits fade te schrijven, joepie, weer minder werk ;)


15 Jan 1997
===========

Intro
~~~~~
Het   intro   scherm   heeft   nu   op  de  achtergrond  een
kleurverloop,  dat gerealiseerd wordt dmv. de copper (what a
surprise). Hiervoor wordt kleur 31 systematisch verminkt van
Y=0..79  (ofwel vanaf raster 44..123). Hierna wordt kleur 31
weer op de oorspronkelijke waarde terug gezet.


18 Jan 1997
===========

DrawPlayer
~~~~~~~~~~
De  animatie-frames  voor  de  speler  zijn eindelijk af (of
tenminste  bijna).  Daarom  is  DrawPlayer  aangepast  om de
juiste frame weer te geven, afh. van speler-positie.

Eerst bepalen we in welke kolom de speler zit:

	Kolom = (Plyr_X - (32-12) ) / 24

	Let op dat Plyr_X aangepast wordt: 32 is de minimale
	x-positie op het scherm. 24 is de kolombreedte.
	Omdat Plyr_X en Y het midden aangeven van de images
	moet het getal 32 nog met de helft van de
	kolombreedte verminderd worden, met 12 dus.

Dan bepalen we de rij waarin de speler zich bevindt:

	Rij = (Plyr_Y - (32-8)) / 16

	Ook hier is 32 de minimale positie. De kolombreedte
	is nu echter 16...

Het frame-nummer is dan: FrameNr = Kolom*5 + Rij

Waar  de  animatie-frame images te vinden zijn is opgeslagen
in een tabel: Ship_Anim. Hierin staan de 25 pointers naar de
data.  Met  a2  naar  Ship_Anim  wijzend lezen we dus juiste
pointer dan uit met (a0,FrameNr.w)!


Schaduwen
~~~~~~~~~
Voor  de  schaduwen geldt hetzelfde als hierboven, alleen nu
hebben we maar 5 frames (frames 5,10,15,20 en 25 maar dan in
het  zwart).  De tabel heet nu Shadow_Anim. Om de schaduw te
tekenen  is  dus  alleen maar het nummer van de kolom van de
speler nodig; de y-positie ligt altijd vast...


Engines
~~~~~~~
Bij   player   datastructuur  zijn  twee  nieuwe  variabelen
bijgekomen:  Plyr_EngineCnt  en Plyr_EngineOn. De laatste is
een  boolean:  TRUE (1) dan wordt Plyr_EngineCnt automatisch
verhoogd  (in  DrawPlayer()), bij FALSE wordt Plyr_EngineCnt
automatisch verlaagd.

Plyr_EngineCnt  is  de  offset in de tabel EnginePal. Dit is
een tabel met 8 longs (24-bit HLi kleur). Mbv. deze tabel en
de   offset  kopieert  DrawPlayer()  de  gewenste  kleur  in
kleurregister 63 van de copperlist.

Er  is  speciaal voor deze aanpak gekozen omdat de engine nu
eenvoudig  te  starten  is  door EngineCnt op 0 te zetten en
EngineOn  op  TRUE.  Er  zijn  namelijk nog andere situaties
(bijv.  LevelInit, LevelDone, Collision, etc.) maar de motor
aan of uit moet worden gezet. Nu kan dat lekker simpel.

Note:  tijdens evt. faden moet het kopieeren van de kleur in
de copperlist eigenlijk wel uit worden gezet...



21 Jan 1997
===========

RealTime
~~~~~~~~
Om  het  spel  realtime te maken hoeven we geen erg precieze
timer  te  hebben: we leggen altijd een geheel aantal frames
af, vanwege de aanroep naar WaitVBL !

In  de VBLHandler wordt nu steeds een teller RealTimeCnt met
1  opgehoogd. In de GameLoop slaan we (na WaitVBL) de waarde
van deze teller op in RealTime en resetten we RealTimeCnt.

In  RealTime  staat  nu  dus het aantal frames dat de vorige
loop  kostte.  Nu  vermenigvuldigen  we  de Accel's met deze
waarde.

Note:  we  gaan  hier  uit  van  50 frames/sec. Omdat we dan
altijd  een  geheel  aantal  frames  af  leggen, kunnen alle
variabelen gewoon integers blijven. Zouden we uitgaan van 25
frames/sec dan moesten we RealTime nog eens door 2 delen, en
dan  zouden  we  uit kunnen komen op 1.5 frames, etc. en dan
zouden de variabelen allemaal fixed point moeten worden (Spd
en posities ook)...


Requirements
~~~~~~~~~~~~
Het  spel  is  met  een  chipmem-only machine gewoon niet te
spelen, dus fastmem is een vereiste (1 meg is genoeg)...

De requirements zijn nu dus:
	o AGA
	o PAL
	o Fastmem


DoPlayer
~~~~~~~~
Als  de  Game_Flags  gelijk zijn aan Flg_Normal dan wordt de
joystick  input  verwerkt,  waarna de keyboard input komt en
daarna  wordt de speler verplaatst. Als de Game_Flags anders
zijn  dan  wordt  de  joystick  input overgeslagen. Keyboard
input  en  move komen dan wel voor...(behalve bij Pause, die
zorgt zelf voor keyboard input...)


Intro probleempje
~~~~~~~~~~~~~~~~~
Eerste  keer  opstarten  gaat alles goed, maar na het spelen
van een level stonden er allemaal verticale strepen door het
intro   scherm.  Het  bleek  dat  dit  sprites  waren.  Door
sprite-dma  uit  te zetten en spritepointers met 0 te vullen
enzo gingen de sprites niet weg.

De oplossing: sprite-dma aan en spritepointers naar een lege
sprite  laten wijzen... Misschien staan de sprites er nu nog
wel, maar je ziet je in ieder geval niet meer...


Intro
~~~~~
Intro_Flags geven nu aan in welk deel van het intro we bezig
zijn:  text,  ask  name of track select. Warp heeft nu eigen
variabelen: Intro_WarpIn en Intro_WarpOut. Dit zijn booleans
(TRUE/FALSE)  die  aangeven  of  we  aan het warpen zijn. Ze
kunnen natuurlijk niet beide TRUE zijn, wel FALSE...


22 Jan 1997
===========

Music
~~~~~
Beetje  aangepaste versie van pt-ciaplay.s gebruikt (met CIA
timing  dus).  CIA-B interrupt aanzetten en audio-dma. CIA-A
interrupt  niet,  want  dan gaat de keyboard-handler van het
O.S. gewoon nog door (in ieder geval die van Asm-One ;).

Mijn  aanpassing  is  dat  mt_data  een  pointer is naar een
module,  zodat  er meerdere mods gebruikt kunnen worden. Bij
de  init van het programma wordt SetCIAInt aangeroepen om de
interrupt  handler te installeren. Bij het afsluiten doen we
een beroep op ResetCIAInt.

Om een mod af te spelen doen we: 

	move.l #adres,mt_data		; juiste pointer
	bsr.w	mt_init			; init
	st	mt_Enable		; daar gaan we
	...
	bsr.w	mt_end			; stoppen

Als mt_Enable false is (sf mt_Enable) dan stopt de muziek.


Probleempjes
~~~~~~~~~~~~
Het bleek dat als mt_init aangeroepen werd (st mt_Enable was
nog  niet eens nodig, het instellen van CIA-B uiteraard wel)
dat dan cop0 verminkt werd.

Na  het  reserveren  van 8 bytes _voor_ dat cop0 gealloceerd
werd  was  het  probleem  verholpen.  Als minder dan 4 bytes
gealloceerd  werden  dan  werd cop0 verminkt. Maar we moeten
tenminste   8   bytes   alloceren   omdat   de   copperlists
QUAD-aligned moeten zijn (vanwege fetchmode 3).

De  oplossing  ??? Extra 8 bytes alloceren werkt so far goed
als temporary solution. So far ;)


Level-structuur
~~~~~~~~~~~~~~~
Level		8 bytes per level
Wall_Z		MaxWall*4 bytes per level
Wall_Type	MaxWall*2 bytes per level

waar MaxWall = 128...
 

23 Jan 1997
===========

Palette
~~~~~~~
Omdat  het  voor  fades  etc.  makkelijker is om het palette
gewoon  in  het  24-bits  formaat  te  hebben: $00RrGgBb, is
SetPal24  nu  aangepast.  De  24-bits kleur wordt omgerekend
naar  de  high  12bits  en  low  12bits die vervolgens in de
copperlist worden gepropt.

De   palettes   moeten  nu  met  AgaIff  als  24-bit  worden
weggeschreven, niet als 24-bit HLi ofzo...


Cross-fading
~~~~~~~~~~~~
Er zijn twee routines die de cross-fading voor hun rekening
nemen: 
	InitXFade -> heeft als parameter Stepsize. Zet de
		     variabelen XFadeStep en XFadeCount op
		     hun beginwaarde.
	XFade     -> zorgt voor de actuele fading. Zolang
                     de fading nog niet klaar is, blijft
                     XFadeCount positief. Als deze negatief
		     is, dan wordt de routine geskipt.
		   
Voordat we beginnen met faden moet InitXFade dus aangeroepen
worden.  Om dan echt te faden moet XFade elke vertical blank
aangeroepen  worden.  Dit mag ook nog steeds gebeuren als de
fade klaar is, de routine heeft dit zelf in de gaten en exit
dan meteen.

Note  dat XFade alleen het source _palette_ veranderd. Na de
aanroep  van XFade moet dus ook nog een SetPal24 call volgen
om het palette ook echt in de copperlist te gooien.

XFade  verandert  het  source  palette,  dus  dit  moet  een
tijdelijke array zijn, niet het originele palette...


Notes voor fading
~~~~~~~~~~~~~~~~~
Het  bleek dat cross-fades etc. helemaal niet gaaf zijn (bij
het  intro  kan  het niet, vanwege rainbow in copperlist) en
bij  het  spel  is  het niet mooi. Of het moet misschien een
hele snelle zijn, maar daar heb ik geen zin in.

De  crossfade wordt alleen gebruikt bij collision. Dit wordt
door  de VBL-interrupt handler afgehandeld, zodat dit altijd
realtime gebeurd.


Intro notes
~~~~~~~~~~~
Zodat planes 7 en 8 aan het begin van het intro goed terecht
komen,  worden  hun  pointer  in  Intro  naar  de copperlist
geschreven, ipv. in InitMain...

De  Intro_Flags  bepalen  waar  we  in  het intro zitten. In
InitMain  worden  ze  op  IFlg_Text  gezet, zodat we met het
begin  der  beginnen  beginnen, huhuh. Na het spelen van een
level  wordt  Intro_Flags  gelijk  gemaakt aan IFlg_TrackSel
zodat  we naar het track select gedeelte gaan en niet steeds
opnieuw  een  naam  hoeven  in  te  voeren. Helemaal opnieuw
beginnen  kan  wel,  door ESC in te drukken tijdens de track
select...


28 Jan 1997
===========

Probleempjes
~~~~~~~~~~~~
Het   bleek  dat  vanaf  ikoon  opstarten  niet  lukte.  Het
copieeren van de command-line parameters bij WBInit bleek de
boosdoener.  Na  het  verwijderen van dat stukje code werkte
alles wel goed.


Cheatmode
~~~~~~~~~
Na  het  intypen  van  MOTHERGOOSE als naam krijg je bij elk
level  9  ships en 9:59 minuten tijd. InitLevel() vergelijkt
de passwords (op z'on manier dat het niet meevalt om met een
hex-editor  als  Zap te zoeken) en maakt Plyr_Cheat TRUE als
de cheatcode klopt, FALSE in het andere geval.

Dit  is  nodig,  omdat  we  bij  DoPlayer,  in het LevelDone
gedeelte  ook rekening moeten houden met de veranderde tijd.
Bij  het  bepalen  van de BestTime wordt namelijk Level_Time
weer  gebruikt,  maar  dat  klopt niet, we moeten nu ook van
9:59   uitgaan.   Daarom   wordt   hier  ook  op  Plyr_Cheat
gechecked...


Tijd
~~~~
Tijdens  een  collision blijft de tijd ook gewoon doorlopen,
anders is het maken van een paar collisions soms sneller dan
gewoon  "netjes"  vliegen,  en  dat mag niet. Alleen als dan
weer "PRESS FIRE" gaat knipperen stopt de tijd...

