Jump to content
IGNORED

Formula 18A / Formula 99 / Formula 99+ development


Asmusr

Recommended Posts

This is the development thread for the F18A racing demo.

 

 

Only a few weeks ago I had no idea how 8-bit racing games like Pole Position were created. I had a vague idea that the screen was drawn from polygons like most 3D games today. Then I found this excellent page that explains a lot.

 

The starting point is an image of the road:

 

post-35226-0-23062200-1460752847_thumb.png

 

The image is 512 pixels wide - twice as wide as a normal TI screen - so if we center the view this gives us 128 pixels on each side for scrolling the view from side to side. The F18A allows us to create such a view by setting the page size to 2X1 in VREG 29 (>1D). Once we have done that we can scroll the entire screen left and right by varying the horizontal scroll offset in VREG 27 (>1B) between 0 and 255 (128 is the center).

 

Note that there are no stripes in the road yet, these will be added later by changing the palette.

 

More to follow...

Edited by Asmusr
  • Like 10
Link to comment
Share on other sites

After drawing the basic road in Paint Shop Pro I imported it into Magellan. I decided to use the F18A 8 color mode (ECM 3). I also added a few more details, mountains and clouds copied from the Pole Position arcade game. Later I also imported the sprites for the car, and all is included in the attached Magellan file.

 

post-35226-0-02220700-1460755443_thumb.png

 

I then used the Export/Assembler Data option in Magellan to produce the attached road.a99 assembly file.

 

More to follow...

road2.mag

road.a99

  • Like 2
Link to comment
Share on other sites

Next step is to make the road look like we're moving. There are two components in this: the alternating color bands that seem to be moving towards you and the curvature of the road. If we had a full bitmap screen and a fast enough processor one way to obtain this effect would be to recalculate and update the bottom part of the screen every frame. But even though the GPU in the F18A might be fast enough we only have 256 8x8 patterns to draw the screen. Fortunately the F18A allows us to use a trick that was also used back in the days: we can change scroll offset and colors for each scanline while the screen is drawn. Scanline effects are really difficult on a stock TI-99/4A because the CPU and the VDP are not in sync, but on the F18A we have a feature called the HSYNC trigger that allows code to be performed each time a scanline has been drawn.

 

So both color bands and curvature are accomplished by scanline effects: color bands by alternating the color palette and curvature by scrolling each individual scanline horizontally by a calculated amount. At any time the base image stays exactly the same, which you would see if you could stop the F18A GPU from performing its task.

 

More to follow...

  • Like 6
Link to comment
Share on other sites

In the demo, almost everything is done by the F18A GPU. To start the GPU, first you need to upload your code to VDP RAM. Then you need to set 2 VDP registers to tell the GPU where to start.

*	   Init GPU routine
*	   Set the GPU PC which also triggers it
	   LI   R0,GPUPRG/256+>3600
	   BL   @VWTR
	   LI   R0,>3700
	   BL   @VWTR

It used to be difficult to produce code in CPU RAM bound for VDP RAM, but not any longer with the XORG feature of XAS99.

 

Here is an example of a GPU scanline program, IDLE makes the GPU idle until its activated again by a new HSYNC.

*
* F18A GPU program
*
GPUPRG LI   R15,>47FE                   ; Set up stack pointer
*      Set the HSYNC trigger
       LI   R0,>4000
       MOVB R0,@>6032                   ; Write to VR 50
RETURN IDLE
*      HSYNC routine
       CLR  R1                          ; For storing scanline
       MOVB @>7000,R1                   ; Get scanline no
       SWPB R1                          ; Swap to LSB
       INC  R1                          ; Changes affect next line since HSYNC is triggered at end
       CI   R1,240                      ; Check if last scanline (30 row mode)
       JGT  RETURN                      ; Greater - return
       JLT  GPU1                        ; Smaller - continue
       CLR  R1                          ; Equals - wrap to 0
*      Now R1 holds the actual affected scanline
*      Set palette
       LI   R0,>5010                    ; Palette reg 8
       MOV  @Z,R1
       ANDI R1,>0020                    ; 0-31 pal 0, 64-32 pal 1
       SRL  R1,1
       AI   R1,PALS                     ; Add palette base
       LI   R2,8
PLOOP  MOV  *R1+,*R0+                   ; Copy palette word
       DEC  R2
       JNE  PLOOP
       JMP  RETURN                      ; Return
*// GPUPRG

More to follow...

  • Like 4
Link to comment
Share on other sites

I'm still working through the info on Lou's Pseudo 3d Page and improving the demo at each step.

 

The first step was to calculate a map that records the depth (z-value) of each scanline of the road. The map is used to generate more accurate road stripes (color bands) and later for displaying sprites on the road. The formula is something like:

 

z = yCamera / (yScanline - yHorizon)

 

I used a Java program to generate the assembly code for the map, which ended up like this:

ZTBL   DATA >FFFF,>F5C2,>EC4E,>E38D,>DB6D,>D3DC,>CCCC,>C631
       DATA >BFFF,>BA2E,>B4B4,>AF8A,>AAAA,>A60D,>A1AF,>9D89
       DATA >9999,>95DA,>9249,>8EE2,>8BA2,>8888,>8590,>82B9
       DATA >8000,>7D63,>7AE1,>7878,>7627,>73EC,>71C7,>6FB5
       DATA >6DB6,>6BCA,>69EE,>6822,>6666,>64B8,>6318,>6186
       DATA >6000,>5E86,>5D17,>5BB3,>5A5A,>590B,>57C5,>5689
       DATA >5555,>542A,>5307,>51EB,>50D7,>4FCA,>4EC5,>4DC5
       DATA >4CCD,>4BDA,>4AED,>4A06,>4924,>4848,>4771,>469F
       DATA >45D1,>4508,>4444,>4384,>42C8,>4210,>415C,>40AC
       DATA >4000,>3F57,>3EB1,>3E0F,>3D70,>3CD5,>3C3C,>3BA6
       DATA >3B13,>3A83,>39F6,>396B,>38E3,>385E,>37DB,>375A
       DATA >36DB,>365F,>35E5,>356D,>34F7,>3483,>3411,>33A1
       DATA >3333,>32C7,>325C,>31F3,>318C,>3127,>30C3,>3061

There are 104 entries in the map - one for each road scanline. As you can see I scaled the z value so that the top scanline of the road has the maximum value >FFFF.

 

Now when you have a scanline you can easily look up the z value. You simply pick one of the most significant bits from the z value to determine the color of the road stripes/color band for the given scanline.

 

Perhaps it's not easy to see from this screenshot, but the result looks much better:

 

post-35226-0-45786000-1461861253_thumb.png

 

More to follow...

 

 

  • Like 3
Link to comment
Share on other sites

Next step was to replace the sine curve demo road with a 'real' track. If you think of the track as a list of road segments of equal length (like a toy race track), it turns out that all the information you need to store is the curvature for each segment. So a long track can be stored in only a few bytes.

 

What is the curvature? Well, if you draw a straight like between two y values, the x value will change with the same amount for each step:

x=x+dx

If you want to draw a curve instead, the amount you add to x should also change for each step:

x=x+dx
dx=dx+ddx

ddx is the constant curvature that you can save for each track segment.

 

As Lou explains, you will get the most interesting view by showing two segments on the screen at the same time: at the bottom of the screen you show the segment on which the car is currently driving, and at the top you show the next incoming segment (which might have another curvature). For each frame the top segment moves further down the screen (at a steady speed) until it covers the entire screen. At that point the top segment becomes the current segment and a new segment starts appearing at the top of the screen and so forth...

 

By rolling the horizon (hills/clouds) in the opposite direction in the opposite direction of the road curvature (of the bottom segment) you will get an intensified impression of the road turning.

 

I have also added basic steering using joystick 1.

 

I better stop rambling and post the demo as it is now. :)

 

 

 

 

 

 

racing.dsk

racing.a99

  • Like 3
Link to comment
Share on other sites

Here's a video. Look much smoother on hardware or JS99'er.

 

 

And here's the entire, surprisingly short GPU screen drawing routine.

HILLSY EQU  120
ROADY1 EQU  136
ROADY2 EQU  239
ROADH  EQU  ROADY2-ROADY1+1
HEIGHT EQU  240

GPUPR1 XORG GPUPRG
       LI   R15,>47FE                   ; Set up stack pointer
*      Set the HSYNC trigger
       LI   R0,>4000
       MOVB R0,@>6032                  ; Write to VR 50
       JMP  GPU0
RETURN IDLE
*      HSYNC routine
       CLR  R3                         ; For storing scanline
       MOVB @>7000,R3                  ; Get scanline no
       SWPB R3                         ; Swap to LSB
       INC  R3                         ; Changes affect next line since HSYNC is at end
       CI   R3,HEIGHT                  ; Check if last scanline
       JGT  RETURN                     ; Greater - return
       JLT  GPUP14                     ; Smaller - continue
GPU0   CLR  R3                         ; Equals - wrap to 0
*      Now R3 holds the actual affected scanline
*      After last scanline, i.e. before scanline 0, setup next screen
*      Loop through scanlines, bottom to top, and calculate the X (scroll) offset
*      Also keep track of min and max values
       LI   R0,(ROADH-1)*2+XOFFS       ; Point to X offset for last scanline
       LI   R1,ROADY2                  ; Scanline number
       LI   R2,ROADH                   ; Number of road scanlines
       MOV  @XPOS,R4                   ; X (FP 10.6) (currently just zero)
       S    @XCAR,R4                   ; Subtract players desired car position
       CLR  R5                         ; DX (FP 10.6)
       LI   R6,>7FFF                   ; Min X, set to max int
       LI   R7,->8000                  ; Max X, set to min int
       MOV  @SEGPOS,R10                ; Segment position as FP 14.2
       SRL  R10,2                      ; Convert to integer
GPUP1  MOV  R4,*R0                     ; Save X offset for scanline
       C    R4,R6                      ; Compare to minimum
       JGT  GPUP2                      ; Greater - continue
       MOV  R4,R6                      ; Save as minimum
GPUP2  C    R4,R7                      ; Compare to maximum
       JLT  GPUP3                      ; Smaller - continue
       MOV  R4,R7                      ; Save as maximum
GPUP3  C    R1,R10                     ; Compare scanline number to segment position
       JL   GPUP4                      ; If smaller we are in the top segment
       MOV  @SEGBOT,R8                 ; Get bottom segment pointer
       JMP  GPUP5
GPUP4  MOV  @SEGTOP,R8                 ; Get top segment pointer
GPUP5  A    *R8,R5                     ; DX += DDX
       A    R5,R4                      ; X += DX
       DECT R0
       DEC  R1
       DEC  R2
       JNE  GPUP1
*      Check min and max values
       CLR  R9                         ; Reset X adjustment
       AI   R6,160*64                  ; Min X + 160 (FP 10.6)
       JGT  GPUP6                      ; If zero or greater
       JEQ  GPUP6                      ; it's within range -160 - 159
       MOV  R6,R9                      ; Otherwise save adjustment
       JMP  GPUP7
GPUP6  AI   R7,-159*64                 ; Max X - 159 (FP 10.6)
       JLT  GPUP7                      ; If zero or smaller
       JEQ  GPUP7                      ; it's within range -160 - 159
       MOV  R7,R9                      ; Otherwise save adjustment
*      Loop again to adjust X values
GPUP7  SRA  R9,1                       ; Divide adjustment by 2
       JEQ  GPUP9                      ; Skip if zero
       LI   R0,XOFFS
       LI   R2,ROADH
GPUP8  MOV  *R0,R4                     ; Get X offset
       S    R9,R4                      ; Adjust
       MOV  R4,*R0+                    ; Write X offset
       DEC  R2
       JNE  GPUP8
*      Adjust sprite in opposite direction
       SRA  R9,6                       ; Convert to integer
       NEG  R9
       AI   R9,128-32
       JGT  GPU20
       CLR  R9
       JMP  GPU21
GPU20  CI   R9,256-64
       JLT  GPU21
       LI   R9,256-64
GPU21  SWPB R9
       MOVB R9,@SPRATB+1
*      Clouds and hills offsets
GPUP9  MOV  @SEGBOT,R0
       MOV  *R0,R0                     ; Get curvature for bottom segment (FP 10.6)
       MPY  @SPEED,R0                  ; Multiply by speed (FP 10.6)
       SRA  R1,2                       ; Scale
       S    R1,@CLOUDX
       SLA  R1,1                       ; Hills move with double speed of clouds
       S    R1,@HILLSX
*      Update position
       MOV  @SPEED,R0
       SRL  R0,4
       A    R0,@ZPOS
*      Move segment
       MOV  @SEGPOS,R1
       SRL  R0,3
       A    R0,R1
GPUP10 CI   R1,HEIGHT*4                ; Bottom scanline in FP 14.2
       JLE  GPUP13
*      Bottom segment out of screen
       MOV  @SEGTOP,R0
       MOV  R0,@SEGBOT                 ; Bottom segment = top segment
       CI   R0,SEGLSE                  ; Check if top segment is last in list
       JEQ  GPUP11
       INCT R0                         ; Not last - get next
       JMP  GPUP12
GPUP11 LI   R0,SEGLST                  ; Last - get first
GPUP12 MOV  R0,@SEGTOP                 ; Set top segment
       LI   R1,ROADY1*4                ; Top road scanline in FP 14.2
*      Save segment position
GPUP13 MOV  R1,@SEGPOS                 ; Set segment position
*      Save a copy of curvature for bottom segment
       MOV  @SEGBOT,R0
       MOV  *R0,@CURVAT
*      Scrolling: branch on landscape type
GPUP14 CI   R3,ROADY1                  ; Is it road?
       JHE  GPUP16                     ; Yes - jump to road
       CI   R3,HILLSY                  ; Is it hills?
       JHE  GPUP15                     ; Yes - jump to hills
*      Clouds / sky
       MOV  @CLOUDX,R1
       JMP  GPUP17
*      Hills
GPUP15 MOV  @HILLSX,R1
       JMP  GPUP17
*      Road
GPUP16 MOV  R3,R1                      ; Scanline
       AI   R1,-ROADY1                 ; Translate first road scanline to 0
       SLA  R1,1                       ; Convert to word
       MOV  @XOFFS(R1),R1              ; Get offset from table
*      Scroll scanline
GPUP17 LI   R2,>2000                   ; Center of horizontal scrolling in FP 10.6
       S    R1,R2                      ; Subtract scanline offset
       MOV  R2,R1                      ; Copy
       SLA  R1,2                       ; Convert to FP 8.8
       MOVB R1,@>601B                   ; Set scroll register
       ANDI R2,>4000                   ; Isolate bit that decides name table
       SRL  R2,6                       ; Move bit into place
       MOVB R2,@>6002                  ; Set name table register to 0 or 1
*      Road markings
       CI   R3,ROADY1                  ; Test scanline
       JLT  GPUP19                     ; No road above line ROADH1
*      Get scanline depth from map
       MOV  R3,R1                      ; Get the scanline
       AI   R1,-ROADY1                 ; Translate first road scanline to 0
       SLA  R1,1                       ; Convert to word
       MOV  @ZTBL(R1),R1               ; Get depth from table
*      Decide which palette to use
       MOV  @ZPOS,R2                   ; Get position
       SLA  R2,6                       ; Convert to 8.8 FP
       A    R2,R1                      ; Add position to make road marking move
       ANDI R1,>1000                   ; 0000-0FFF pal 0, 1000-1FFF pal 1
       SRL  R1,8                       ; Shift bit to fit size of palette
       AI   R1,PALS                    ; Add palette base
*      Set palette
       LI   R2,8                       ; Number of colors/words in a palette
       LI   R0,>5010                   ; Palette reg 8
GPUP18 MOV  *R1+,*R0+                  ; Copy palette word
       DEC  R2                         ; Counter
       JNE  GPUP18                     ; Copy loop
*      Return
GPUP19 B   @RETURN

Next step is to add more sprites...

  • Like 6
Link to comment
Share on other sites

I used a few extra minutes that I should not have, but I noticed two weird things.

 

#1: It did not work in Classic99, dunno why, maybe I'm using an older version.

 

#2: I tried loading it via the HDX and it did not load. This is the first time I've

experienced something like that. I'll figure it out later when I have time.

I'm guessing but it may have something to do with the stored order.

 

Once I copied the files to my HxC acting as DSK1 it worked flawlessly. :thumbsup:

 

"Top Notch" so far Rasmus!

Link to comment
Share on other sites

Wow! That's awesome. I notice you have three (it seems) three "stripes" of clouds. It might be interesting to give them a parallax effect. Make the bottom clouds largest, the next row slightly smaller, and the top row smaller still, and move them horizontally at progressively slower speeds for the parallax effect.

 

Amazing demo and a great showcase for the F18

Link to comment
Share on other sites

  • 4 weeks later...
  • 2 weeks later...

Wow! That's awesome. I notice you have three (it seems) three "stripes" of clouds. It might be interesting to give them a parallax effect. Make the bottom clouds largest, the next row slightly smaller, and the top row smaller still, and move them horizontally at progressively slower speeds for the parallax effect.

 

Amazing demo and a great showcase for the F18

Actually, it might look better in reverse. The smaller clouds on the bottom to give the illusion of distance. The larger top clouds would move slower. This is just a hunch.

Link to comment
Share on other sites

Actually, it might look better in reverse. The smaller clouds on the bottom to give the illusion of distance. The larger top clouds would move slower. This is just a hunch.

 

I agree that the smaller clouds should be closest to the horizon, but the largest ones should move fastest.

Link to comment
Share on other sites

  • 1 month later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...