Finished fleshing out text for part 1.

This commit is contained in:
Martin Haye 2013-10-05 11:52:30 -07:00
parent ae0a02f557
commit a512a48ff3
4 changed files with 136 additions and 35 deletions

View File

@ -39,11 +39,9 @@ Finally he created the image we saw earlier, by picking a color, picking a drawi
...
```
If you're familiar with the actual bytes that form high-res graphics on the Apple II you may recognize some of these values. I think Big bBue is actually two of the bits in "A885D5" but that's not terribly important. What is important is that the pixels are stored here in a format that can be easily read by other programs. How did those bits get in the file? Let's take a very brief look at the code.
If you're familiar with the actual bytes that form high-res graphics on the Apple II you may recognize some of these values. I think Big Blue is actually two of the bits somewhere in "A885D5" but it's not terribly important. What is important is that the pixels are stored here in a format that can be easily read by other programs. How did those bits get in the file? Let's take a very brief look at the code.
Outlaw Editor is written in Java, and here's the Java code that actually twiddles the bits:
[Direct code link](https://github.com/badvision/lawless-legends/search?q=BigBlue1_10)
Outlaw Editor is written in Java, and here's the Java code that actually twiddles the bits. [code link](https://github.com/badvision/lawless-legends/search?q=BigBlue1_10)
```Java
public void set(boolean on, int x, int y) {
@ -56,6 +54,111 @@ Outlaw Editor is written in Java, and here's the Java code that actually twiddle
}
```
It gets called by ``plot`` which figures out based on the X and Y coordinates where on the current pattern it should get the bits. [code link](https://github.com/badvision/lawless-legends/search?q=BigBlue1_20)
```Java
public void plot(int x, int y, int[] pattern, boolean hiBitMatters) {
if (x < 0 || y < 0 || x >= getWidth() * 7 || y >= getHeight()) {
return;
}
int pat = pattern[(y % 16) * 4 + ((x / 7) % 4)];
set((pat & (1 << (x % 7))) != 0, x, y);
```
That in turn got called by ``performAction`` that interprets the mouse click. [code link](https://github.com/badvision/lawless-legends/search?q=BigBlue1_30)
```Java
public boolean performAction(boolean alt, boolean released, int x, int y) {
...
switch (currentDrawMode) {
...
case Pencil1px:
if (canSkip) {
return false;
}
plot(x, y, currentFillPattern, hiBitMatters);
redrawScanline(y);
...
```
We looked at the XML data which is great for portability but not very compact -- it's 96 kbytes! What we need to do is pack that data down into an efficient format for use on the Apple II. For that we have another Java program called PackMap. We run it from the command-line and here's how it goes. ![Packing the data](packing.png)
It reads in all the images, does some fancy manipulation of them, then writes them out to a little file (only 8 Kbytes now) and formed of tightly packed bytes.
```
0000000 4d 11 11 21 01 00 00 00 00 04 06 04 03 00 00 00
0000010 00 00 00 05 00 00 00 04 05 04 00 00 00 00 05 04
0000020 01 04 03 02 00 04 00 06 00 00 00 02 01 06 00 00
0000030 00 00 00 00 04 00 03 00 03 00 00 00 04 00 05 00
0000040 00 00 00 04 00 00 00 04 00 00 04 00 00 00 05 04
0000050 00 00 00 00 03 01 04 00 06 00 00 02 00 04 00 00
0000060 06 00 00 00 00 02 00 06 00 01 00 06 00 00 01 02
...
0000120 00 00 00 00 00 00 54 55 05 33 33 33 33 33 33 33
0000130 33 33 31 30 30 30 30 30 30 30 30 30 30 30 30 30
0000140 30 30 30 30 30 30 30 30 30 33 33 33 33 33 33 33
0000150 33 33 39 3c 3c 3c 3c 3c 3c 3c 3c 3c 3c 3c 3c 3c
0000160 3c 3c 3c 3c 3c 3c 3c 3c 3c 33 33 33 33 33 33 33
0000170 33 33 39 3c 3c 3c 3c 3c 3c 3c 3c 3c 3c 3c 3c 3c
0000180 3c 3c 3c 3c 3c 3c 3c 3c 3c 33 33 33 33 33 33 33
0000190 33 33 39 3c 3c 3c 3c 3c 3c 3c 3c 3c 3c 3c 33 33
...
```
The first part has a header, then the map tiles (just numbers now, not names), followed by all the texture data.
The code for PackMap is in a dialect of Java called Groovy. It's kind of like Java on steroids. It goes through each image and parses the bits and turns them back into a representation of pixels. [code link](https://github.com/badvision/lawless-legends/search?q=BigBlue1_40)
```Groovy
def pixelize(dataEl)
{
...
return (0..<nLines).collect { lineNum ->
...
for (def byteNum in 0..<nBytes) {
def pos = (lineNum*nBytes + byteNum) * 2 // two hex chars per byte
def val = Integer.parseInt(hexStr[pos..pos+1], 16)
for (def bitNum in 0..6) {
if (pixBits == 0) // [ref BigBlue1_40]
pix = (val & 0x80) ? 4 : 0 // grab high bit of first byte of pix
...
}
}
return outRow
}
}
```
Then what it does is create several versions of each image, at progressively lower and lower resolutions. These are called "mipmaps", which basically stores a full-size version, then a half-size version, then quarter-size, eighth-size, etc. If you want to learn more about those and why we use them you can wait for part 4, or read the Wikipedia page now. http://en.wikipedia.org/wiki/Mipmap
![Example of Mipmapping](http://upload.wikimedia.org/wikipedia/commons/5/5c/MipMap_Example_STS101.jpg)
Then it writes out all that compact data we saw earlier using this code. It creates a header with the length and a type, then the raw data bytes. [code link](https://github.com/badvision/lawless-legends/search?q=BigBlue1_50)
```Groovy
def writeMap(stream, rows, names) {
...
// Header: one-char code followed by two-byte length
// (length should not include the 5-byte header)
//
def len = width*height
stream.write((int)'M') // for "Map"
stream.write(width)
stream.write(height)
stream.write(len & 0xFF)
stream.write((len>>8) & 0xFF)
// After the header comes the raw data
rows.each { row ->
row.each { tile ->
stream.write(names.findIndexOf { it == tile?.@name } + 1)
}
}
}
```
That's it for part one. In the next part of the story we'll talk about how we figure out where to show Big Blue on the screen.
Part 2: Casting Rays
--------------------
@ -81,20 +184,20 @@ The basic idea is that shoot a bunch of virtual "rays" from the player's eye in
Let's take a brief look at the code to do this. This is written in Javascript, making it easy to test changes to it before porting them to the Apple II.
The player has a position, X and Y, and a direction, shown in the code marked [BigBlue2a](https://github.com/badvision/lawless-legends/search?q=BigBlue2a).
The player has a position, X and Y, and a direction, shown in the code marked [code link](https://github.com/badvision/lawless-legends/search?q=BigBlue2a).
```javascript
// Player attributes [ref BigBlue2a]
// Player attributes
var player = {
x : 11.0, // current x, y position
y : 10.5,
dir : 0, // the direction that the player is turning, either -1 for left or 1 for right.
```
For efficiency we perform as much math as possible at startup and stick the results into tables, that's done here. Lots of trigonometric functions and square roots so it's good to do this once instead of each time we have to draw the screen. You don't have to understand the math, this is just so you can get a feel for where it is and what it looks like. [BigBlue2b](https://github.com/badvision/lawless-legends/search?q=BigBlue2b)
For efficiency we perform as much math as possible at startup and stick the results into tables, that's done here. Lots of trigonometric functions and square roots so it's good to do this once instead of each time we have to draw the screen. You don't have to understand the math, this is just so you can get a feel for where it is and what it looks like. code link](https://github.com/badvision/lawless-legends/search?q=BigBlue2b)
```javascript
// Set up data tables prior to rendering [ref BigBlue2b]
// Set up data tables prior to rendering
function initCast()
{
var i;
@ -103,19 +206,19 @@ function initCast()
precastData = [];
```
When you press a key, like to move forward, this code gets called and decides that to do, like update the player's X/Y coordinate or direction. [BigBlue2c](https://github.com/badvision/lawless-legends/search?q=BigBlue2c)
When you press a key, like to move forward, this code gets called and decides that to do, like update the player's X/Y coordinate or direction. [code link](https://github.com/badvision/lawless-legends/search?q=BigBlue2c)
```javascript
switch (e.keyCode) { // which key was pressed? [ref BigBlue2c]
switch (e.keyCode) { // which key was pressed?
case 38: // up, move player forward, ie. increase speed
player.speed = 1;
```
Then this code cycles through each ray and draws it. [BigBlue2d](https://github.com/badvision/lawless-legends/search?q=BigBlue2d)
Then this code cycles through each ray and draws it. [code link](https://github.com/badvision/lawless-legends/search?q=BigBlue2d)
```javascript
// Cast all the rays from the player position and draw them [ref BigBlue2d]
// Cast all the rays from the player position and draw them
function castRays(force)
{
// If we're already showing this location and angle, no need to re-do it.
@ -123,11 +226,10 @@ function castRays(force)
player.x == prevX &&
```
The complicated math is handled in a separate function. This code traces an individual ray from the player's eye until it hits something on the map. [BigBlue2e](https://github.com/badvision/lawless-legends/search?q=BigBlue2e)
The complicated math is handled in a separate function. This code traces an individual ray from the player's eye until it hits something on the map. [code link](https://github.com/badvision/lawless-legends/search?q=BigBlue2e)
```javascript
// Cast one ray from the player's position through the map until we hit a wall.
// [ref BigBlue2e]
// This version uses only integers, making it easier to port to the 6502.
function intCast(x)
{
@ -138,10 +240,10 @@ function intCast(x)
var wRayPosX = sword(player.x * 256);
```
The results of all this math for a given horizontal coordinate are: (1) the wall type, the coordinate left-to-right on that wall's texture, and the height of the column to draw. [BigBlue2f](https://github.com/badvision/lawless-legends/search?q=BigBlue2f)
The results of all this math for a given horizontal coordinate are: (1) the wall type, the coordinate left-to-right on that wall's texture, and the height of the column to draw. [code link](https://github.com/badvision/lawless-legends/search?q=BigBlue2f)
```javascript
// Wrap it all in a nice package. [ref BigBlue2f]
// Wrap it all in a nice package.
return { wallType: map[bMapY][bMapX],
textureX: bWallX / 256.0,
height: lineHeight };
@ -160,10 +262,10 @@ Let's do some pixel calisthenics! In part 3 we're going to see how all that ray-
So I showed you a bunch of ray casting code in Javascript. Let's take a quick look at the 6502 assembly language code that does the same stuff. You don't have to understand it all, but it's good to know where it is and roughly what it does.
First, we set up the player's position and direction. We store each coordinate in two bytes: the low byte is the fractional part (0..255, so $80 = 128 = 0.5), and the high byte is the whole part (1, 2, etc.).
[BigBlue3_10](https://github.com/badvision/lawless-legends/search?q=BigBlue3_10)
[code link](https://github.com/badvision/lawless-legends/search?q=BigBlue3_10)
```Assembly
; Establish the initial player position and direction [ref BigBlue3_10]
; Establish the initial player position and direction
setPlayerPos:
; X=1.5
lda #1
@ -181,10 +283,10 @@ setPlayerPos:
rts
```
Remember those logarithm tables we created in the Javascript code? And the table of vectors for each possible angle? They're just encoded directly here rather than computed on the 6502. [BigBlue3_20](https://github.com/badvision/lawless-legends/search?q=BigBlue3_20)
Remember those logarithm tables we created in the Javascript code? And the table of vectors for each possible angle? They're just encoded directly here rather than computed on the 6502. [code link](https://github.com/badvision/lawless-legends/search?q=BigBlue3_20)
```Assembly
; Table to translate an unsigned byte to 3+5 bit fixed point log2 [ref BigBlue3_20]
; Table to translate an unsigned byte to 3+5 bit fixed point log2
tbl_log2_b_b:
.byte $00,$00,$00,$00,$00,$07,$0C,$11,$15,$19,$1C,$1F,$22,$24,$27,$29
.byte $2B,$2D,$2E,$30,$32,$33,$34,$36,$37,$38,$3A,$3B,$3C,$3D,$3E,$3F
@ -192,12 +294,12 @@ tbl_log2_b_b:
;...etc...
```
[BigBlue3_30](https://github.com/badvision/lawless-legends/search?q=BigBlue3_30)
[code link](https://github.com/badvision/lawless-legends/search?q=BigBlue3_30)
```Assembly
; Precalculated ray initialization parameters. One table for each of the 16 angles.
; Each angle has 63 rays, and each ray is provided with 4 parameters (one byte each param):
; dirX, dirY, deltaX, deltaY. [ref BigBlue3_30]
; dirX, dirY, deltaX, deltaY.
precast_0:
.byte $72,$C7,$3E,$7C
.byte $72,$C9,$3D,$7E
@ -212,10 +314,10 @@ precast_1:
;...etc...
```
Here's the code to process a keypress from the player. [BigBlue3_40](https://github.com/badvision/lawless-legends/search?q=BigBlue3_40)
Here's the code to process a keypress from the player. [code link](https://github.com/badvision/lawless-legends/search?q=BigBlue3_40)
```Assembly
; Dispatch the keypress [ref BigBlue3_40]
; Dispatch the keypress
: cmp #'W' ; 'W' for forward
bne :+
jsr moveForward
@ -233,11 +335,10 @@ Here's the code to process a keypress from the player. [BigBlue3_40](https://git
```
When we need to re-draw, this code steps through each ray, calculating the texture number, coordinate, and height, then drawing it.
[BigBlue3_50](https://github.com/badvision/lawless-legends/search?q=BigBlue3_50)
[code link](https://github.com/badvision/lawless-legends/search?q=BigBlue3_50)
```Assembly
; Calculate the height, texture number, and texture column for one ray
; [ref BigBlue3_50]
@oneCol:
stx pMap ; set initial map pointer for the ray
sty pMap+1
@ -253,11 +354,11 @@ When we need to re-draw, this code steps through each ray, calculating the textu
```
And finally there's a whole bunch of code that does all that complicated math we don't understand. I'm not going to explain all the code in depth, other than to say it does the same thing the Javascript code did... just using a lot more lines!
[BigBlue3_60](https://github.com/badvision/lawless-legends/search?q=BigBlue3_60)
[code link](https://github.com/badvision/lawless-legends/search?q=BigBlue3_60)
```Assembly
;-------------------------------------------------------------------------------
; Cast a ray [ref BigBlue3_60]
; Cast a ray
; Input: pRayData, plus Y reg: precalculated ray data (4 bytes)
; playerX, playerY (integral and fractional bytes of course)
; pMap: pointer to current row on the map (mapBase + playerY{>}*height)
@ -290,10 +391,10 @@ You may have watched an image being loaded onto the Apple II hi-res screen. You'
We don't want weird and complex, we need simple and fast. So our we throw some smarts at the problem to isolate all the complexity to a small part of our program, so the rest of the program doesn't have to worry about it. We use a technique called "unrolling". That's a little program that writes a big repetetive program into memory. In our case the unrolled routine has a block of code for every line on the screen, and each block contains an instruction or two for each kind of bit pair on that line. Code outside simply sticks the pixels in very regular and easy-to-calculate places, then calls the unrolled loop to blast everything onto the screen at high speed. In programming circles we call that blasting process a "bit blit" (stands for Bit-Level Block Transfer).
Here's the template for one block of the blit. This template gets copied and repeated for each line on the screen, substituting different addresses for the screen lines. [BigBlue3_70](https://github.com/badvision/lawless-legends/search?q=BigBlue3_70)
Here's the template for one block of the blit. This template gets copied and repeated for each line on the screen, substituting different addresses for the screen lines. [code link](https://github.com/badvision/lawless-legends/search?q=BigBlue3_70)
```Assembly
; Template for blitting code [ref BigBlue3_70]
; Template for blitting code
blitTemplate: ; comments show byte offset
lda decodeTo57 ; 0: pixel 3
asl ; 3: save half of pix 3 in carry
@ -311,7 +412,7 @@ blitTemplate: ; comments show byte offset
; 29 bytes total
```
All those ``lda`` and ``ora`` instructions are actually performing table lookups. The tables are aligned so that the low byte of the address is the actual value to look up. Here are the table addresses in memory, and the address of the unrolled code. [BigBlue3_75](https://github.com/badvision/lawless-legends/search?q=BigBlue3_75)
All those ``lda`` and ``ora`` instructions are actually performing table lookups. The tables are aligned so that the low byte of the address is the actual value to look up. Here are the table addresses in memory, and the address of the unrolled code. [code link](https://github.com/badvision/lawless-legends/search?q=BigBlue3_75)
```Assembly
; Main-mem tables and buffers

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -241,7 +241,7 @@ public class AppleImageEditor extends ImageEditor implements EventHandler<MouseE
if (canSkip) {
return false;
}
plot(x, y, currentFillPattern, hiBitMatters);
plot(x, y, currentFillPattern, hiBitMatters); // [ref BigBlue1_30]
redrawScanline(y);
break;
case Pencil3px:
@ -328,7 +328,7 @@ public class AppleImageEditor extends ImageEditor implements EventHandler<MouseE
if (x < 0 || y < 0 || x >= getWidth() * 7 || y >= getHeight()) {
return;
}
int pat = pattern[(y % 16) * 4 + ((x / 7) % 4)];
int pat = pattern[(y % 16) * 4 + ((x / 7) % 4)]; // [ref BigBlue1_20]
set((pat & (1 << (x % 7))) != 0, x, y);
if (hiBitMatters) {
setHiBit(pat >= 128, x, y);

View File

@ -42,7 +42,7 @@ class PackMap
def pos = (lineNum*nBytes + byteNum) * 2 // two hex chars per byte
def val = Integer.parseInt(hexStr[pos..pos+1], 16)
for (def bitNum in 0..6) {
if (pixBits == 0)
if (pixBits == 0) // [ref BigBlue1_40]
pix = (val & 0x80) ? 4 : 0 // grab high bit of first byte of pix
if (val & (1<<bitNum))
pix |= (1<<pixBits)
@ -164,7 +164,7 @@ class PackMap
}
def writeMap(stream, rows, names)
def writeMap(stream, rows, names) // [ref BigBlue1_50]
{
def width = rows[0].size()
def height = rows.size()