From f73b03e821feb8b76ea5f8a25aa09c5cceeeb19b Mon Sep 17 00:00:00 2001 From: Vince Weaver Date: Fri, 6 Jan 2023 14:17:40 -0500 Subject: [PATCH] budge3d: convert to assembly --- graphics/hgr/budge3d/Makefile | 11 +- graphics/hgr/budge3d/asm_example/Makefile | 35 + graphics/hgr/budge3d/asm_example/budge3d.dsk | Bin 0 -> 143360 bytes graphics/hgr/budge3d/asm_example/hardware.inc | 17 + graphics/hgr/budge3d/asm_example/hello.bas | 2 + graphics/hgr/budge3d/asm_example/hgr_tables.s | 102 ++ .../hgr/budge3d/asm_example/math_constants.s | 68 + .../hgr/budge3d/asm_example/scale_constants.s | 113 ++ graphics/hgr/budge3d/asm_example/shapes.s | 208 +++ graphics/hgr/budge3d/asm_example/ship_cube.s | 1269 +++++++++++++++++ graphics/hgr/budge3d/asm_example/zp.inc | 33 + graphics/hgr/budge3d/basic_example/Makefile | 40 + .../hgr/budge3d/basic_example/hardware.inc | 17 + graphics/hgr/budge3d/basic_example/hello.bas | 3 + .../hgr/budge3d/basic_example/hgr_tables.s | 102 ++ .../budge3d/{ => basic_example}/hgr_textgen.s | 0 .../budge3d/basic_example/math_constants.s | 68 + .../budge3d/basic_example/scale_constants.s | 113 ++ graphics/hgr/budge3d/basic_example/shapes.s | 208 +++ .../hgr/budge3d/basic_example/ship_cube.s | 1170 +++++++++++++++ .../hgr/budge3d/{ => basic_example}/test.bas | 0 graphics/hgr/budge3d/basic_example/zp.inc | 33 + graphics/hgr/budge3d/hardware.inc | 10 - graphics/hgr/budge3d/hello.bas | 3 +- graphics/hgr/budge3d/ship_cube.s | 193 ++- 25 files changed, 3751 insertions(+), 67 deletions(-) create mode 100644 graphics/hgr/budge3d/asm_example/Makefile create mode 100644 graphics/hgr/budge3d/asm_example/budge3d.dsk create mode 100644 graphics/hgr/budge3d/asm_example/hardware.inc create mode 100644 graphics/hgr/budge3d/asm_example/hello.bas create mode 100644 graphics/hgr/budge3d/asm_example/hgr_tables.s create mode 100644 graphics/hgr/budge3d/asm_example/math_constants.s create mode 100644 graphics/hgr/budge3d/asm_example/scale_constants.s create mode 100644 graphics/hgr/budge3d/asm_example/shapes.s create mode 100644 graphics/hgr/budge3d/asm_example/ship_cube.s create mode 100644 graphics/hgr/budge3d/asm_example/zp.inc create mode 100644 graphics/hgr/budge3d/basic_example/Makefile create mode 100644 graphics/hgr/budge3d/basic_example/hardware.inc create mode 100644 graphics/hgr/budge3d/basic_example/hello.bas create mode 100644 graphics/hgr/budge3d/basic_example/hgr_tables.s rename graphics/hgr/budge3d/{ => basic_example}/hgr_textgen.s (100%) create mode 100644 graphics/hgr/budge3d/basic_example/math_constants.s create mode 100644 graphics/hgr/budge3d/basic_example/scale_constants.s create mode 100644 graphics/hgr/budge3d/basic_example/shapes.s create mode 100644 graphics/hgr/budge3d/basic_example/ship_cube.s rename graphics/hgr/budge3d/{ => basic_example}/test.bas (100%) create mode 100644 graphics/hgr/budge3d/basic_example/zp.inc diff --git a/graphics/hgr/budge3d/Makefile b/graphics/hgr/budge3d/Makefile index 9c891078..3ed3216e 100644 --- a/graphics/hgr/budge3d/Makefile +++ b/graphics/hgr/budge3d/Makefile @@ -7,10 +7,9 @@ EMPTY_DISK = ../../../empty_disk all: budge3d.dsk -budge3d.dsk: HELLO TEST SHIP_CUBE +budge3d.dsk: HELLO SHIP_CUBE cp $(EMPTY_DISK)/empty.dsk budge3d.dsk $(DOS33) -y budge3d.dsk SAVE A HELLO - $(DOS33) -y budge3d.dsk SAVE A TEST $(DOS33) -y budge3d.dsk BSAVE -a 0x300 SHIP_CUBE @@ -19,10 +18,6 @@ budge3d.dsk: HELLO TEST SHIP_CUBE HELLO: hello.bas $(TOKENIZE) < hello.bas > HELLO -### - -TEST: test.bas - $(TOKENIZE) < test.bas > TEST #### @@ -32,9 +27,9 @@ SHIP_CUBE: ship_cube.o ship_cube.o: ship_cube.s \ zp.inc hardware.inc \ shapes.s math_constants.s hgr_tables.s \ - hgr_textgen.s scale_constants.s + scale_constants.s ca65 -o ship_cube.o ship_cube.s -l ship_cube.lst #### clean: - rm -f *~ *.o *.lst HELLO SHIP_CUBE TEST + rm -f *~ *.o *.lst HELLO SHIP_CUBE diff --git a/graphics/hgr/budge3d/asm_example/Makefile b/graphics/hgr/budge3d/asm_example/Makefile new file mode 100644 index 00000000..98d69639 --- /dev/null +++ b/graphics/hgr/budge3d/asm_example/Makefile @@ -0,0 +1,35 @@ +include ../../../../Makefile.inc + +DOS33 = ../../../../utils/dos33fs-utils/dos33 +TOKENIZE = ../../../../utils/asoft_basic-utils/tokenize_asoft +LINKER_SCRIPTS = ../../../../linker_scripts +EMPTY_DISK = ../../../../empty_disk + +all: budge3d.dsk + +budge3d.dsk: HELLO SHIP_CUBE + cp $(EMPTY_DISK)/empty.dsk budge3d.dsk + $(DOS33) -y budge3d.dsk SAVE A HELLO + $(DOS33) -y budge3d.dsk BSAVE -a 0x300 SHIP_CUBE + + +### + +HELLO: hello.bas + $(TOKENIZE) < hello.bas > HELLO + + +#### + +SHIP_CUBE: ship_cube.o + ld65 -o SHIP_CUBE ship_cube.o -C $(LINKER_SCRIPTS)/apple2_300.inc + +ship_cube.o: ship_cube.s \ + zp.inc hardware.inc \ + shapes.s math_constants.s hgr_tables.s \ + scale_constants.s + ca65 -o ship_cube.o ship_cube.s -l ship_cube.lst + +#### +clean: + rm -f *~ *.o *.lst HELLO SHIP_CUBE diff --git a/graphics/hgr/budge3d/asm_example/budge3d.dsk b/graphics/hgr/budge3d/asm_example/budge3d.dsk new file mode 100644 index 0000000000000000000000000000000000000000..5d05047535af65c2a354d7a6cd2f7ee23240f1b5 GIT binary patch literal 143360 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 literal 0 HcmV?d00001 diff --git a/graphics/hgr/budge3d/asm_example/hardware.inc b/graphics/hgr/budge3d/asm_example/hardware.inc new file mode 100644 index 00000000..db86e1b0 --- /dev/null +++ b/graphics/hgr/budge3d/asm_example/hardware.inc @@ -0,0 +1,17 @@ +;CODE_arr = $6008 ; CODE array (dummy address) +;X_arr = $602f ; X array (dummy address) +;Y_arr = $6056 ; Y array (dummy address) +;SCALE_arr = $607d ; SCALE array (dummy address) +;XROT_arr = $60a4 ; XROT array (dummy address) +;YROT_arr = $60cb ; YROT array (dummy address) +;ZROT_arr = $60f2 ; ZROT array (dummy address) +;SX_arr = $6119 ; SX array (dummy address) +;SY_arr = $6140 ; SY array (dummy address) + +; soft switches +TXTCLR = $c050 ; RW display graphics +MIXCLR = $c052 ; RW display full screen +TXTPAGE1 = $c054 ; RW display page 1 +TXTPAGE2 = $c055 ; RW display page 2 (or read/write aux mem) +HIRES = $c057 ; RW display hi-res graphics + diff --git a/graphics/hgr/budge3d/asm_example/hello.bas b/graphics/hgr/budge3d/asm_example/hello.bas new file mode 100644 index 00000000..fba2ddfb --- /dev/null +++ b/graphics/hgr/budge3d/asm_example/hello.bas @@ -0,0 +1,2 @@ +10 PRINT CHR$ (4)"BRUN SHIP_CUBE" + diff --git a/graphics/hgr/budge3d/asm_example/hgr_tables.s b/graphics/hgr/budge3d/asm_example/hgr_tables.s new file mode 100644 index 00000000..3fa45f1d --- /dev/null +++ b/graphics/hgr/budge3d/asm_example/hgr_tables.s @@ -0,0 +1,102 @@ +; +; Divide-by-7 table. Used to divide the X coordinate (0-255) by 7, yielding a +; byte offset for the hi-res screen column. +; +Div7Tab: ; 1400 + .byte $00,$00,$00,$00,$00,$00,$00,$01,$01,$01,$01,$01,$01,$01,$02,$02 + .byte $02,$02,$02,$02,$02,$03,$03,$03,$03,$03,$03,$03,$04,$04,$04,$04 + .byte $04,$04,$04,$05,$05,$05,$05,$05,$05,$05,$06,$06,$06,$06,$06,$06 + .byte $06,$07,$07,$07,$07,$07,$07,$07,$08,$08,$08,$08,$08,$08,$08,$09 + .byte $09,$09,$09,$09,$09,$09,$0a,$0a,$0a,$0a,$0a,$0a,$0a,$0b,$0b,$0b + .byte $0b,$0b,$0b,$0b,$0c,$0c,$0c,$0c,$0c,$0c,$0c,$0d,$0d,$0d,$0d,$0d + .byte $0d,$0d,$0e,$0e,$0e,$0e,$0e,$0e,$0e,$0f,$0f,$0f,$0f,$0f,$0f,$0f + .byte $10,$10,$10,$10,$10,$10,$10,$11,$11,$11,$11,$11,$11,$11,$12,$12 + .byte $12,$12,$12,$12,$12,$13,$13,$13,$13,$13,$13,$13,$14,$14,$14,$14 + .byte $14,$14,$14,$15,$15,$15,$15,$15,$15,$15,$16,$16,$16,$16,$16,$16 + .byte $16,$17,$17,$17,$17,$17,$17,$17,$18,$18,$18,$18,$18,$18,$18,$19 + .byte $19,$19,$19,$19,$19,$19,$1a,$1a,$1a,$1a,$1a,$1a,$1a,$1b,$1b,$1b + .byte $1b,$1b,$1b,$1b,$1c,$1c,$1c,$1c,$1c,$1c,$1c,$1d,$1d,$1d,$1d,$1d + .byte $1d,$1d,$1e,$1e,$1e,$1e,$1e,$1e,$1e,$1f,$1f,$1f,$1f,$1f,$1f,$1f + .byte $20,$20,$20,$20,$20,$20,$20,$21,$21,$21,$21,$21,$21,$21,$22,$22 + .byte $22,$22,$22,$22,$22,$23,$23,$23,$23,$23,$23,$23,$24,$24,$24,$24 + +; +; Hi-res bit table. Converts the X coordinate (0-255) into a bit position +; within a byte. (Essentially 2 to the power of the remainder of the coordinate +; divided by 7.) +; + +HiResBitTab: ; 1500 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08 + +; +; Hi-res Y-coordinate lookup table, low byte. Values 0-191 are meaningful, 192- +; 255 are junk. +; + +YTableLo: ; 1600 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$80,$80,$80,$80,$80,$80,$80,$80,$00,$00,$00,$00,$00,$00,$00,$00,$80,$80,$80,$80,$80,$80,$80,$80 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$80,$80,$80,$80,$80,$80,$80,$80,$00,$00,$00,$00,$00,$00,$00,$00,$80,$80,$80,$80,$80,$80,$80,$80 + .byte $28,$28,$28,$28,$28,$28,$28,$28,$a8,$a8,$a8,$a8,$a8,$a8,$a8,$a8,$28,$28,$28,$28,$28,$28,$28,$28,$a8,$a8,$a8,$a8,$a8,$a8,$a8,$a8 + .byte $28,$28,$28,$28,$28,$28,$28,$28,$a8,$a8,$a8,$a8,$a8,$a8,$a8,$a8,$28,$28,$28,$28,$28,$28,$28,$28,$a8,$a8,$a8,$a8,$a8,$a8,$a8,$a8 + .byte $50,$50,$50,$50,$50,$50,$50,$50,$d0,$d0,$d0,$d0,$d0,$d0,$d0,$d0,$50,$50,$50,$50,$50,$50,$50,$50,$d0,$d0,$d0,$d0,$d0,$d0,$d0,$d0 + .byte $50,$50,$50,$50,$50,$50,$50,$50,$d0,$d0,$d0,$d0,$d0,$d0,$d0,$d0,$50,$50,$50,$50,$50,$50,$50,$50,$d0,$d0,$d0,$d0,$d0,$d0,$d0,$d0 + + +.align $0100 ; (64 bytes) + +;============================================== +; Hi-res Y-coordinate lookup table, high byte. +;============================================== + +YTableHi: + .byte $00,$04,$08,$0c,$10,$14,$18,$1c + .byte $00,$04,$08,$0c,$10,$14,$18,$1c + .byte $01,$05,$09,$0d,$11,$15,$19,$1d + .byte $01,$05,$09,$0d,$11,$15,$19,$1d + + .byte $02,$06,$0a,$0e,$12,$16,$1a,$1e + .byte $02,$06,$0a,$0e,$12,$16,$1a,$1e + .byte $03,$07,$0b,$0f,$13,$17,$1b,$1f + .byte $03,$07,$0b,$0f,$13,$17,$1b,$1f + + .byte $00,$04,$08,$0c,$10,$14,$18,$1c + .byte $00,$04,$08,$0c,$10,$14,$18,$1c + .byte $01,$05,$09,$0d,$11,$15,$19,$1d + .byte $01,$05,$09,$0d,$11,$15,$19,$1d + + .byte $02,$06,$0a,$0e,$12,$16,$1a,$1e + .byte $02,$06,$0a,$0e,$12,$16,$1a,$1e + .byte $03,$07,$0b,$0f,$13,$17,$1b,$1f + .byte $03,$07,$0b,$0f,$13,$17,$1b,$1f + + .byte $00,$04,$08,$0c,$10,$14,$18,$1c + .byte $00,$04,$08,$0c,$10,$14,$18,$1c + .byte $01,$05,$09,$0d,$11,$15,$19,$1d + .byte $01,$05,$09,$0d,$11,$15,$19,$1d + + .byte $02,$06,$0a,$0e,$12,$16,$1a,$1e + .byte $02,$06,$0a,$0e,$12,$16,$1a,$1e + .byte $03,$07,$0b,$0f,$13,$17,$1b,$1f + .byte $03,$07,$0b,$0f,$13,$17,$1b,$1f + +.align $0100 ; (64 bytes) + diff --git a/graphics/hgr/budge3d/asm_example/math_constants.s b/graphics/hgr/budge3d/asm_example/math_constants.s new file mode 100644 index 00000000..a0e38fa5 --- /dev/null +++ b/graphics/hgr/budge3d/asm_example/math_constants.s @@ -0,0 +1,68 @@ +; +; Math constants for rotation. +; +; To compute X * cos(theta), start by converting theta (0-27) into a table base +; address (using the 28-byte tables RotIndexLo_cos / RotIndexHi_cos). Split the +; X coordinate into nibbles, use the low 4 bits to index into the adjusted +; RotTabLo pointer, and the high 4 bits to index into the adjusted RotTabHi +; pointer. Add the values at those locations together. +; +; This is similar to the way the scale table works. See ScaleTableLo, below, +; for a longer explanation of how the nibbles are used. +; +; As an example, suppose we have a point at (36,56), and we want to rotate it 90 +; degrees (rot=7). We use the RotIndex tables to get the table base addresses: +; sin=$00/$00 ($1200/$1300), cos=$70/$07 ($1270/$1307). We split the +; coordinates into nibbles without shifting ($24,$38 --> $20 $04, $30 $08), and +; use the nibbles as indexes into the tables: +; +; X * cos(theta) = ($1274)+($1327) = $00+$00 = 0 +; Y * sin(theta) = ($1208)+($1330) = $08+$30 = 56 +; X * sin(theta) = ($1204)+($1320) = $04+$20 = 36 +; Y * cos(theta) = ($1278)+($1337) = $00+$00 = 0 +; +; XC = X*cos(theta) - Y*sin(theta) = -56 +; YC = X*sin(theta) + Y*cos(theta) = 36 +; +; which is exactly what we expected (counter-clockwise). +; +; The largest value from the index table is $EE, so that last 17 bytes in each +; table are unused. +; + +RotTabLo: ; 1200 + .byte $00,$01,$02,$03,$04,$05,$06,$07,$08,$09,$0a,$0b,$0c,$0d,$0e,$0f + .byte $00,$01,$02,$03,$04,$05,$06,$07,$08,$09,$0a,$0b,$0c,$0d,$0e,$0f + .byte $00,$01,$02,$03,$04,$05,$05,$06,$07,$08,$09,$0a,$0b,$0c,$0d,$0e + .byte $00,$01,$02,$02,$03,$04,$05,$05,$06,$07,$08,$09,$09,$0a,$0b,$0c + .byte $00,$01,$01,$02,$02,$03,$04,$04,$05,$06,$06,$07,$07,$08,$09,$09 + .byte $00,$00,$01,$01,$02,$02,$03,$03,$03,$04,$04,$05,$05,$06,$06,$07 + .byte $00,$00,$00,$01,$01,$01,$01,$02,$02,$02,$02,$02,$03,$03,$03,$03 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $00,$00,$00,$ff,$ff,$ff,$ff,$fe,$fe,$fe,$fe,$fe,$fd,$fd,$fd,$fd + .byte $00,$00,$ff,$ff,$fe,$fe,$fd,$fd,$fd,$fc,$fc,$fb,$fb,$fa,$fa,$f9 + .byte $00,$ff,$ff,$fe,$fe,$fd,$fc,$fc,$fb,$fa,$fa,$f9,$f9,$f8,$f7,$f7 + .byte $00,$ff,$fe,$fe,$fd,$fc,$fb,$fb,$fa,$f9,$f8,$f7,$f7,$f6,$f5,$f4 + .byte $00,$ff,$fe,$fd,$fc,$fb,$fb,$fa,$f9,$f8,$f7,$f6,$f5,$f4,$f3,$f2 + .byte $00,$ff,$fe,$fd,$fc,$fb,$fb,$fa,$f8,$f7,$f6,$f5,$f4,$f3,$f2,$f1 + .byte $00,$ff,$fe,$fd,$fc,$fb,$fa,$f9,$f8,$f7,$f6,$f5,$f4,$f3,$f2,$f1 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + +RotTabHi: ; 1300 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $10,$10,$0e,$0d,$0a,$07,$04,$00,$fc,$f9,$f6,$f3,$f2,$f0,$f0,$00 + .byte $20,$1f,$1d,$19,$14,$0e,$07,$00,$f9,$f2,$ec,$e7,$e3,$e1,$e0,$00 + .byte $30,$2f,$2b,$26,$1e,$15,$0b,$00,$f5,$eb,$e2,$da,$d5,$d1,$d0,$00 + .byte $40,$3e,$3a,$32,$28,$1c,$0e,$00,$f2,$e4,$d8,$ce,$c6,$c2,$c0,$00 + .byte $50,$4e,$48,$3f,$32,$23,$12,$00,$ee,$dd,$ce,$c1,$b8,$b2,$b0,$00 + .byte $60,$5e,$56,$4b,$3c,$2a,$15,$00,$eb,$d6,$c4,$b5,$aa,$a2,$a0,$00 + .byte $70,$6d,$65,$58,$46,$31,$19,$00,$e7,$cf,$ba,$a8,$9b,$93,$90,$00 + .byte $80,$83,$8d,$9c,$b0,$c8,$e4,$00,$1c,$38,$50,$64,$73,$7d,$80,$00 + .byte $90,$93,$9b,$a8,$ba,$cf,$e7,$00,$19,$31,$46,$58,$65,$6d,$70,$00 + .byte $a0,$a2,$aa,$b5,$c4,$d6,$eb,$00,$15,$2a,$3c,$4b,$56,$5e,$60,$00 + .byte $b0,$b2,$b8,$c1,$ce,$dd,$ee,$00,$12,$23,$32,$3f,$48,$4e,$50,$00 + .byte $c0,$c2,$c6,$ce,$d8,$e4,$f2,$00,$0e,$1c,$28,$32,$3a,$3e,$40,$00 + .byte $d0,$d1,$d5,$da,$e2,$eb,$f5,$00,$0b,$15,$1e,$26,$2b,$2f,$30,$00 + .byte $e0,$e1,$e3,$e7,$ec,$f2,$f9,$00,$07,$0e,$14,$19,$1d,$1f,$20,$00 + .byte $f0,$f0,$f2,$f3,$f6,$f9,$fc,$00,$04,$07,$0a,$0d,$0e,$10,$10,$00 + diff --git a/graphics/hgr/budge3d/asm_example/scale_constants.s b/graphics/hgr/budge3d/asm_example/scale_constants.s new file mode 100644 index 00000000..a6eafba1 --- /dev/null +++ b/graphics/hgr/budge3d/asm_example/scale_constants.s @@ -0,0 +1,113 @@ +; +; Math constants for scaling. +; +; Each table has 16 sets of 16 entries, with one set for each of the 16 possible +; scale values. The values within a set determine how one 4-bit nibble of the +; coordinate is scaled. +; +; Suppose you want to scale the value 100 ($64) by scale factor 8 (a bit over +; half size). We begin by using self-modifying code to select the table +; subsets. This is done in a clever way to avoid shifting. The instructions +; that load from ScaleTabLo are modified to reference $1800, $1810, $1820, and +; so on. The instructions that load from ScaleTabHi reference $1900, $1901, +; $1902, etc. The offset comes from the two 16-byte ScaleIndex tables. For a +; scale factor of 8, we'll be using $1880 and $1908 as the base addresses. +; +; To do the actual scaling, we mask to get the low part of the value ($04) and +; index into ScaleTabLo, at address $1884. We mask the high part of the value +; ($60) and index into ScaleTabHi, at $1968. We add the values there ($02, $36) +; to get $38 = 56, which is just over half size as expected. +; +; This is an approximation, but so is any integer division, and it's done in +; 512+32=544 bytes instead of the 16*256=4096 bytes that you'd need for a fully- +; formed scale. For hi-res graphics it's certainly good enough. +; +; 32 = $20 = ($1880)+($1928) = 18 (.563) +; 40 = $28 = ($1888)+($1928) = 22 (.55) +; 47 = $2F = ($188F)+($1928) = 26 (.553) +; 48 = $30 = ($1880)+($1938) = 27 (.563) +; 100 = $64 = ($1884)+($1968) = 56 (.56) +; + +ScaleTabLo: + .byte $00,$00,$00,$00,$00,$00,$00,$00 + .byte $00,$00,$00,$00,$00,$00,$00,$00 + .byte $00,$00,$00,$00,$00,$00,$00,$00 + .byte $01,$01,$01,$01,$01,$01,$01,$01 + + .byte $00,$00,$00,$00,$00,$00,$01,$01 + .byte $01,$01,$01,$02,$02,$02,$02,$02 + .byte $00,$00,$00,$00,$01,$01,$01,$01 + .byte $02,$02,$02,$02,$03,$03,$03,$03 + + .byte $00,$00,$00,$00,$01,$01,$01,$02 + .byte $02,$02,$03,$03,$03,$04,$04,$04 + .byte $00,$00,$00,$01,$01,$01,$02,$02 + .byte $03,$03,$03,$04,$04,$04,$05,$05 + + .byte $00,$00,$00,$01,$01,$02,$02,$03 + .byte $03,$03,$04,$04,$05,$05,$06,$06 + .byte $00,$00,$01,$01,$02,$02,$03,$03 + .byte $04,$04,$05,$05,$06,$06,$07,$07 + + .byte $00,$00,$01,$01,$02,$02,$03,$03 + .byte $04,$05,$05,$06,$06,$07,$07,$08 + .byte $00,$00,$01,$01,$02,$03,$03,$04 + .byte $05,$05,$06,$06,$07,$08,$08,$09 + + .byte $00,$00,$01,$02,$02,$03,$04,$04 + .byte $05,$06,$06,$07,$08,$08,$09,$0a + .byte $00,$00,$01,$02,$03,$03,$04,$05 + .byte $06,$06,$07,$08,$09,$09,$0a,$0b + + .byte $00,$00,$01,$02,$03,$04,$04,$05 + .byte $06,$07,$08,$08,$09,$0a,$0b,$0c + .byte $00,$00,$01,$02,$03,$04,$05,$06 + .byte $07,$07,$08,$09,$0a,$0b,$0c,$0d + + .byte $00,$00,$01,$02,$03,$04,$05,$06 + .byte $07,$08,$09,$0a,$0b,$0c,$0d,$0e + .byte $00,$01,$02,$03,$04,$05,$06,$07 + .byte $08,$09,$0a,$0b,$0c,$0d,$0e,$0f + +ScaleTabHi: + .byte $00,$00,$00,$00,$00,$00,$00,$00 + .byte $00,$00,$00,$00,$00,$00,$00,$00 + .byte $01,$02,$03,$04,$05,$06,$07,$08 + .byte $09,$0a,$0b,$0c,$0d,$0e,$0f,$10 + + .byte $02,$04,$06,$08,$0a,$0c,$0e,$10 + .byte $12,$14,$16,$18,$1a,$1c,$1e,$20 + .byte $03,$06,$09,$0c,$0f,$12,$15,$18 + .byte $1b,$1e,$21,$24,$27,$2a,$2d,$30 + + .byte $04,$08,$0c,$10,$14,$18,$1c,$20 + .byte $24,$28,$2c,$30,$34,$38,$3c,$40 + .byte $05,$0a,$0f,$14,$19,$1e,$23,$28 + .byte $2d,$32,$37,$3c,$41,$46,$4b,$50 + + .byte $06,$0c,$12,$18,$1e,$24,$2a,$30 + .byte $36,$3c,$42,$48,$4e,$54,$5a,$60 + .byte $07,$0e,$15,$1c,$23,$2a,$31,$38 + .byte $3f,$46,$4d,$54,$5b,$62,$69,$70 + + .byte $f8,$f0,$e8,$e0,$d8,$d0,$c8,$c0 + .byte $b8,$b0,$a8,$a0,$98,$90,$88,$80 + .byte $f9,$f2,$eb,$e4,$dd,$d6,$cf,$c8 + .byte $c1,$ba,$b3,$ac,$a5,$9e,$97,$90 + + .byte $fa,$f4,$ee,$e8,$e2,$dc,$d6,$d0 + .byte $ca,$c4,$be,$b8,$b2,$ac,$a6,$a0 + .byte $fb,$f6,$f1,$ec,$e7,$e2,$dd,$d8 + .byte $d3,$ce,$c9,$c4,$bf,$ba,$b5,$b0 + + .byte $fc,$f8,$f4,$f0,$ec,$e8,$e4,$e0 + .byte $dc,$d8,$d4,$d0,$cc,$c8,$c4,$c0 + .byte $fd,$fa,$f7,$f4,$f1,$ee,$eb,$e8 + .byte $e5,$e2,$df,$dc,$d9,$d6,$d3,$d0 + + .byte $fe,$fc,$fa,$f8,$f6,$f4,$f2,$f0 + .byte $ee,$ec,$ea,$e8,$e6,$e4,$e2,$e0 + .byte $ff,$fe,$fd,$fc,$fb,$fa,$f9,$f8 + .byte $f7,$f6,$f5,$f4,$f3,$f2,$f1,$f0 + diff --git a/graphics/hgr/budge3d/asm_example/shapes.s b/graphics/hgr/budge3d/asm_example/shapes.s new file mode 100644 index 00000000..5968f815 --- /dev/null +++ b/graphics/hgr/budge3d/asm_example/shapes.s @@ -0,0 +1,208 @@ +;=========================================================== +; +; If configured without the HRCG, the module starts here. * +; +; Note that all tables are page-aligned for performance. * +; +;=========================================================== + +NumObjects: .byte 2 ; number of objects + .byte 35 ; number of points (unused) + .byte 41 ; number of lines (unused) +; +; The next five tables represent the data as it was entered into the shape +; editor. There are two shapes. The first (space shuttle) starts at offset 0, +; has 27 points, and 29 lines. The second (cube) starts immediately after the +; first, has 8 points, and 12 lines. +; + +; 3D mesh X coordinates (-60, -57, ...) +ShapeXCoords: + + ; spaceship + + .byte $c4,$c7,$c7,$c7,$c7,$d6,$d6,$d6 ; $00 + .byte $d6,$f1,$f1,$00,$00,$15,$15,$1e ; $08 + .byte $1e,$1e,$1e,$24,$24,$24,$24,$09 ; $10 + .byte $1b,$15,$1e ; $18 + + ; cube + .byte $fb,$05,$05,$fb,$fb ; $1b + .byte $05,$05,$fb ; $20 + + ; junk + + .byte $ec,$fc,$0c,$18,$28,$30,$44,$50,$74,$7c,$05,$fb,$00,$c4,$ca,$ca,$ca,$ca,$d6,$d6,$d6,$d6,$f1,$f1,$00,$00,$15,$15,$1e + .byte $1e,$1e,$1e,$24,$24,$24,$24,$09,$1b,$15,$1e,$d8,$e8,$f8,$08,$18,$28,$9c,$c4,$ec,$14,$3c,$64,$c9,$37,$b5,$4b,$22,$de,$de,$f2,$0e + .byte $22,$22,$de,$de,$f2,$0e,$22,$28,$39,$46,$d8,$c7,$ba,$00,$00,$00,$4d,$4d,$3f,$3f,$b3,$b3,$c1,$c1,$f9,$07,$07,$f9,$11,$ef,$ef,$11 + .byte $08,$f8,$0a,$f6,$19,$e7,$19,$e7,$00,$fa,$06,$00,$00,$fc,$04,$fc,$04,$fa,$06,$f6,$0a,$fc,$04,$f4,$0c,$fa,$06,$fa,$06,$f6,$0a,$f6 + .byte $0a,$f4,$0c,$f4,$0c,$d0,$30,$d0,$30,$d0,$30,$d0,$30,$d0,$30,$d0,$30,$d3,$06,$fc,$1a,$ba,$00,$da,$03,$16,$1a,$b0,$00,$ba,$02,$10 + .byte $34,$1a,$98,$19,$2b,$da,$03,$1b,$ab,$3b,$a0,$a0,$ab,$a4,$01,$df,$82,$d9,$0b,$f2,$0c,$d8,$06,$06,$2b,$7c,$10,$5b,$08,$3f,$19,$16 + .byte $0f,$01,$9c,$19,$23,$0f,$01,$97,$f2,$18,$24,$00,$0c,$c0,$f8,$06,$ed,$2b,$7c,$42,$1a,$ac,$00,$ba,$5c,$06,$f1,$1a,$03,$00,$da,$06 + +; 3D mesh Y coordinates (0, 3, ...) +ShapeYCoords: + ; spaceship + .byte $00,$03,$03,$fd,$fd,$06,$09,$fa + .byte $f7,$09,$f7,$0f,$f1,$24,$dc,$24 + .byte $dc,$09,$f7,$09,$f7,$06,$fa,$00 + .byte $00,$00,$00 + + ; cube + .byte $fb,$fb,$05,$05,$fb + .byte $fb,$05,$05 + + ; garbage + .byte $d0,$e0,$bc,$b0,$c4,$d8,$d0,$e0,$e0,$d0,$0a,$0a,$22,$00,$05,$05,$fb,$fb,$06,$09,$fa,$f7,$09,$f7,$0f,$f1,$24,$dc,$24 + .byte $dc,$09,$f7,$09,$f7,$06,$fa,$00,$00,$00,$00,$20,$20,$20,$20,$20,$20,$e0,$e0,$e0,$e0,$e0,$e0,$10,$10,$fa,$fa,$f4,$f4,$0c,$20,$20 + .byte $0c,$f4,$f4,$0c,$20,$20,$0c,$00,$00,$00,$00,$00,$00,$28,$39,$46,$f9,$07,$07,$f9,$f9,$07,$07,$f9,$4d,$4d,$3f,$3f,$ef,$ef,$11,$11 + .byte $0e,$0e,$1b,$1b,$11,$11,$e7,$e7,$00,$06,$06,$fa,$0a,$0a,$0a,$0a,$0a,$06,$06,$06,$06,$fa,$fa,$06,$06,$06,$06,$fa,$fa,$04,$04,$fc + .byte $fc,$04,$04,$fc,$fc,$0c,$0c,$f4,$f4,$0c,$0c,$f4,$f4,$0c,$0c,$f4,$f4,$06,$00,$4f,$0c,$d0,$d2,$a3,$02,$00,$2c,$0d,$c5,$e4,$e9,$f4 + .byte $04,$06,$00,$a2,$0d,$c1,$00,$00,$00,$20,$4d,$0a,$a9,$ff,$85,$31,$a0,$00,$a5,$24,$c9,$27,$d0,$09,$20,$8e,$fd,$85,$31,$a9,$06,$85 + .byte $24,$b1,$12,$c5,$30,$d0,$13,$e6,$31,$a4,$31,$c0,$10,$b0,$0b,$be,$f0,$00,$e4,$24,$90,$04,$86,$24,$90,$d6,$20,$ed,$fd,$e6,$12,$d0 + +; 3D mesh Z coordinates (0, 3, ...) +ShapeZCoords: + ; spaceship + .byte $00,$03,$fd,$03,$fd,$09,$fa,$09 + .byte $fa,$fa,$fa,$fa,$fa,$fa,$fa,$fa + .byte $fa,$fa,$fa,$fa,$fa,$09,$09,$09 + .byte $09,$1b,$1b + + ; cube + .byte $fb,$fb,$fb,$fb,$05 + .byte $05,$05,$05 + + ; garbage + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$05,$fb,$05,$fb,$09,$fa,$09,$fa,$fa,$fa,$fa,$fa,$fa,$fa,$fa + .byte $fa,$fa,$fa,$fa,$fa,$09,$09,$09,$09,$1e,$1e,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $00,$e2,$e2,$e0,$e0,$e0,$e0,$fd,$de,$c9,$fd,$de,$c9,$fb,$db,$c6,$c9,$c6,$c6,$c9,$c9,$c6,$c6,$c9,$c6,$c6,$c9,$c9,$28,$28,$2a,$2a + .byte $16,$16,$03,$03,$20,$20,$1e,$1e,$5a,$1e,$1e,$1e,$24,$22,$22,$0c,$0c,$12,$12,$10,$10,$0c,$0c,$e8,$e8,$e2,$e2,$e8,$e8,$fa,$fa,$fa + .byte $fa,$e8,$e8,$e8,$e8,$f4,$f4,$f4,$f4,$ee,$ee,$ee,$ee,$00,$00,$00,$00,$89,$f6,$01,$e2,$10,$27,$e8,$03,$64,$00,$0a,$00,$01,$00,$2b + .byte $35,$25,$37,$00,$4c,$45,$08,$2b,$d7,$02,$58,$36,$01,$f5,$20,$89,$f6,$6c,$3b,$38,$08,$ed,$07,$02,$eb,$f8,$4c,$07,$03,$6c,$38,$ec + .byte $28,$db,$02,$a5,$00,$20,$8e,$0a,$20,$89,$f6,$29,$d7,$03,$e2,$00,$60,$00,$20,$8e,$fd,$20,$ce,$0a,$20,$d5,$0e,$20,$c9,$09,$20,$89 + +; 3D mesh line definition: start points (0, 0, 0, ...) +LineStartPoint: ; b00 + ; spaceship (29 lines) + .byte $00,$00,$00,$00,$01,$02,$03,$04 + .byte $06,$08,$09,$0a,$0b,$0c,$0d,$0e + .byte $0f,$10,$11,$12,$13,$05,$07,$13 + .byte $14,$15,$17,$19,$1a + + ; cube (12 lines) + .byte $1b,$1c,$1d + .byte $1e,$1f,$20,$21,$22,$1b,$1c,$1d + .byte $1e + + ; junk + .byte $26,$27,$28,$29,$2a,$2b,$2d,$2e,$30,$30,$30,$30,$31,$32,$33,$34,$36,$38,$39,$3a,$3b,$3c,$3d + .byte $3e,$3f,$40,$41,$42,$43,$35,$37,$43,$44,$45,$47,$49,$48,$4b,$4c,$4d,$4e,$4f,$50,$4b,$57,$59,$51,$5d,$63,$5d,$5e,$5f,$65,$5f,$60 + .byte $5b,$60,$61,$66,$67,$5c,$5d,$62,$63,$6a,$5e,$5f,$64,$65,$6d,$70,$71,$72,$73,$74,$75,$76,$77,$78,$79,$7a,$7b,$7c,$7d,$7e,$7f,$80 + .byte $81,$7e,$7f,$7e,$7f,$84,$85,$84,$85,$86,$87,$88,$88,$88,$8c,$8c,$8d,$8e,$89,$8a,$91,$92,$93,$94,$93,$94,$8b,$8b,$97,$98,$99,$97 + .byte $98,$99,$9a,$9d,$a1,$a9,$9f,$a3,$ab,$9e,$a2,$aa,$a0,$a4,$ac,$39,$a3,$38,$01,$0a,$a5,$03,$4d,$d6,$03,$4a,$37,$38,$25,$39,$11,$3c + .byte $00,$29,$71,$28,$71,$00,$20,$da,$0b,$4c,$45,$08,$20,$89,$f6,$e8,$11,$3c,$00,$32,$b0,$71,$e0,$71,$22,$00,$6c,$08,$00,$14,$cd,$fe + .byte $00,$20,$ce,$0a,$20,$89,$f6,$29,$3a,$28,$3b,$eb,$00,$20,$8e,$0a,$20,$89,$f6,$29,$38,$2a,$39,$f8,$28,$b9,$f8,$00,$20,$cc,$0b,$20 + +; 3D mesh line definition: end points (1, 2, 3, ...) +LineEndPoint: ; c00 + + ; spaceship (29 lines) + .byte $01,$02,$03,$04,$05,$06,$07,$08 + .byte $09,$0a,$0b,$0c,$0d,$0e,$0f,$10 + .byte $11,$12,$13,$14,$14,$15,$16,$15 + .byte $16,$16,$19,$1a,$18 + + ; cube (12 lines) + .byte $1c,$1d,$1e + .byte $1b,$20,$21,$22,$1f,$1f,$20,$21 + .byte $22 + + ; junk + .byte $27,$28,$29,$2a,$2b,$2c,$2f,$2f,$31,$32,$33,$34,$35,$36,$37,$38,$39,$3a,$3b,$3c,$3d,$3e,$3f + .byte $40,$41,$42,$43,$44,$44,$45,$46,$45,$46,$46,$49,$4a,$4a,$51,$52,$53,$54,$55,$56,$50,$58,$5a,$56,$5e,$64,$63,$64,$60,$66,$65,$66 + .byte $67,$67,$68,$68,$69,$6a,$6a,$6b,$6b,$6c,$6d,$6d,$6e,$6e,$6f,$71,$72,$73,$70,$75,$76,$77,$74,$79,$7a,$7b,$78,$7d,$7e,$7f,$7c,$82 + .byte $83,$81,$80,$85,$84,$80,$81,$60,$5d,$84,$85,$89,$8a,$8b,$8d,$8e,$8f,$90,$91,$92,$93,$94,$95,$96,$97,$98,$9b,$9c,$99,$9a,$9a,$9b + .byte $9c,$9b,$9c,$a5,$a9,$ad,$a7,$ab,$af,$a6,$aa,$ae,$a8,$ac,$b0,$10,$dd,$08,$3f,$19,$6b,$0f,$00,$20,$d0,$09,$20,$0c,$fd,$c9,$d3,$66 + .byte $33,$20,$ce,$0a,$a9,$ff,$85,$2f,$d0,$00,$20,$61,$0c,$20,$89,$f6,$11,$00,$02,$29,$d4,$06,$04,$49,$51,$01,$f8,$4a,$06,$03,$51,$01 + .byte $fa,$21,$3a,$f2,$42,$51,$d3,$07,$fb,$19,$00,$02,$11,$7c,$03,$24,$71,$2a,$71,$00,$20,$7b,$0d,$24,$33,$10,$0c,$20,$0c,$fd,$c9,$83 + +; +; For shape N, the index of the first point. +; +; Shape #0 uses points $00-1A, shape #1 uses points $1B-22. (Data at offsets 2- +; 15 is junk.) +FirstPointIndex: + .byte $00,$1b + .byte $08,$0c,$0d,$1d,$2d,$30,$4b,$5b,$88,$7c,$03,$61,$39,$e9 + +; For shape N, the index of the last point + 1. +LastPointIndex: + .byte $1b,$23 + .byte $0c,$0d,$1d,$2d,$30,$4b,$5b,$88,$b1,$2a,$1a,$00,$02,$ba + +; +; For shape N, the index of the first line. +; +; Shape #0 uses lines $00-1C, shape #1 uses lines $1D-28. (Data at offsets 2-15 +; is junk.) + +FirstLineIndex: + .byte $00,$1d + .byte $0c,$01,$12,$20,$2f,$31,$4e,$58,$8b,$01,$9c,$00,$a5,$16 + +; For shape N, the index of the last point + 1. +LastLineIndex: + .byte $1d,$29 + .byte $12,$01,$20,$2f,$31,$4e,$58,$8b,$af,$fe,$4c,$45,$08,$20 + +; +; Indexes into the rotation tables. One entry for each rotation value (0-27). +; The "low" and "high" tables have the same value at each position, just shifted +; over 4 bits. +; +; Mathematically, cosine has the same shape as sine, but is shifted by PI/2 (one +; quarter period) ahead of it. That's why there are two sets of tables, one of +; which is shifted by 7 bytes. +; +; See the comments above RotTabLo for more details. +; + +RotIndexLo_sin: + .byte $70,$60,$50,$40,$30,$20,$10,$00 + .byte $10,$20,$30,$40,$50,$60,$70,$80 + .byte $90,$a0,$b0,$c0,$d0,$e0,$d0,$c0 + .byte $b0,$a0,$90,$80 + +RotIndexHi_sin: + .byte $07,$06,$05,$04,$03,$02,$01,$00 + .byte $01,$02,$03,$04,$05,$06,$07,$08 + .byte $09,$0a,$0b,$0c,$0d,$0e,$0d,$0c + .byte $0b,$0a,$09,$08 + +RotIndexLo_cos: + .byte $00,$10,$20,$30,$40,$50,$60,$70 + .byte $80,$90,$a0,$b0,$c0,$d0,$e0,$d0 + .byte $c0,$b0,$a0,$90,$80,$70,$60,$50 + .byte $40,$30,$20,$10 + +RotIndexHi_cos: + .byte $00,$01,$02,$03,$04,$05,$06,$07 + .byte $08,$09,$0a,$0b,$0c,$0d,$0e,$0d + .byte $0c,$0b,$0a,$09,$08,$07,$06,$05 + .byte $04,$03,$02,$01 + +; +; Indexes into the scale tables. One entry for each scale value (0-15). See +; the comments above ScaleTabLo for more details. +; +ScaleIndexLo: + .byte $00,$10,$20,$30,$40,$50,$60,$70,$80,$90,$a0,$b0,$c0,$d0,$e0,$f0 + +ScaleIndexHi: + .byte $00,$01,$02,$03,$04,$05,$06,$07,$08,$09,$0a,$0b,$0c,$0d,$0e,$0f + +; +; Junk, pads to end of 256-byte page. +.align $0100 ; (48 bytes) + diff --git a/graphics/hgr/budge3d/asm_example/ship_cube.s b/graphics/hgr/budge3d/asm_example/ship_cube.s new file mode 100644 index 00000000..fcca6fba --- /dev/null +++ b/graphics/hgr/budge3d/asm_example/ship_cube.s @@ -0,0 +1,1269 @@ +;============================================================================ +; Disassembly of a module generated by Bill Budge's 3-D Graphics System and +; Game Tool. +; +; The tool itself is copyright 1980 California Pacific Computer Co. Modules +; may be marketed and sold so long as they provide a credit notice. +; +; The HRCG (code and font) is credited to Christopher Espinosa. +;=========================================================================== +; Disassembly by Andy McFadden, using 6502bench SourceGen v1.6. +; Last updated 2020/03/11 +; +; The manual refers to "points" and "lines" rather than "vertices" and +; "edges". For consistency the same nomenclature is used here. +; +; Two shapes are defined: the space shuttle model from the manual, and a +; simple cube 11 units on a side. The module is configured for XOR drawing + +; This makes extensive use of self-modifying code. Labels that begin with an +; underscore indicate self-modification targets. + +; Note from Vince Weaver +; + I've taken the disassembly and am converting it to assembly language +; I guess it might make more sense to get an assembly language kernel +; from the program, but I'm going to modify the BASIC anyway + + + +;=========================================================================== +; +; You can define up to 16 shapes. Their parameters are stored in the various +; arrays: +; +; CODE_arr+N: 0 (do nothing), 1 (transform & draw), 2 (erase previous, +; transform, draw new), 3 (erase). +; X_arr+N: X coordinate of center (0-255). +; Y_arr+N: Y coordinate of center (0-191). +; SCALE_arr+N: scale factor, 0-15. 15 is full size, 0 is 1/16th. +; XROT_arr+N: rotation about X axis, 0-27. 0 is no rotation, 27 is just shy +; of 360 degrees. +; YROT_arr+N: rotation about Y axis. +; ZROT_arr+N: rotation about Z axis. +; SX_arr+N: (output) X coordinate of last point drawn. +; SY_arr+N: (output) Y coordinate of last point drawn. +; +; The code entry points are: +; RESET: initializes graphics module, clears the screen, switches display +; to primary hi-res page. +; CLR: clears both hi-res screens and switches to primary hi-res page. +; HIRES: turns on primary hi-res page. +; CRUNCH: primary animation function. +; +; The "CRUNCH" function: +; - erases objects whose CODE value is 2 or 3 +; - computes new transformations for objects whose CODE value is 1 or 2 +; - draws objects whose CODE value is 1 or 2 +; - flips the display to the other page +;============================================================================ + +.include "zp.inc" + +.include "hardware.inc" + +; org $0300 + +entry: + jsr RESET + + ; CODE[0]=1 -> transform and draw + ; CODE[1]=1 -> transform and draw + + lda #1 + sta CODE_arr + sta CODE_arr+1 + + ; X[0]=127 Y[0]=96:X[1]=20:Y[1]=30 -> co-ord of center + + lda #127 + sta X_arr + lda #96 + sta Y_arr + + lda #20 + sta X_arr+1 + lda #30 + sta Y_arr+1 + + ; SCALE[0]=15:XROT[0]=2:YROT[0]=5:ZROT[0]=0 + ; SCALE[1]=15:XROT[1]=2:YROT[1]=5:ZROT[1]=0 + + lda #15 + sta SCALE_arr + sta SCALE_arr+1 + + lda #2 + sta XROT_arr + sta XROT_arr+1 + + lda #5 + sta YROT_arr + sta YROT_arr+1 + + lda #0 + sta ZROT_arr + sta ZROT_arr+1 + + jsr CRUNCH + jsr CRUNCH + + ; CODE[0]=2:CODE[1]=2 -> erase and draw new + + lda #2 + sta CODE_arr + sta CODE_arr+1 + +loop: + ; ZROT[0]+=1: IF ZEROT[0]==28 THEN ZROT[0]=0 + ; YROT[1]=ZROT[0] + + inc ZROT_arr + lda ZROT_arr + cmp #28 + bne zrot_ok + lda #0 + sta ZROT_arr +zrot_ok: + sta YROT_arr+1 + + jsr CRUNCH + + jmp loop + + nop +.align $100 ; 400 + nop +.align $100 ; 500 + nop +.align $100 ; 600 + nop +.align $100 ; 700 + nop +.align $100 ; 700 + + +;=========================================================== +; If configured without the HRCG, the module starts here. * +;=========================================================== + +;=========================================================== +; Note that all tables are page-aligned for performance. * +;=========================================================== + +.include "shapes.s" + +; +; These four buffers hold transformed points in screen coordinates. The points +; are in the same order as they are in the mesh definition. +; +; One pair of tables holds the X/Y screen coordinates from the previous frame, +; the other pair of tables holds the coordinates being transformed for the +; current frame. We need two sets because we're display set 0 while generating +; set 1, and after we flip we need to use set 0 again to erase the display. +; +; ---------- +; +; Computed X coordinate, set 0. +XCoord0_0E: ; 0e00 + .byte $00 + .align $100 + +; Computed Y coordinate, set 0. +YCoord0_0F: ; 0f00 + .byte $00 + .align $100 + +; Computed X coordinate, set 1. +XCoord1_10: ; 1000 + .byte $00 + .align $100 + +; Computed Y coordinate, set 1. +YCoord1_11: ; 1100 + .byte $00 + .align $100 + +.include "math_constants.s" + +.include "hgr_tables.s" + +.include "scale_constants.s" + +; +; Draw a list of lines using exclusive-or, which inverts the pixels. Drawing +; the same thing twice erases it. +; +; On entry: +; $45 - index of first line +; $46 - index of last line +; XCoord_0E/YCoord_0F or XCoord_10/YCoord_11 have transformed points in screen +; coordinates +; +; When the module is configured for OR-mode drawing, this code is replaced with +; a dedicated erase function. The erase code is nearly identical to the draw +; code, but saves a little time by simply zeroing out whole bytes instead of +; doing a read-modify-write. +; + +; Clear variables + +DrawLineListEOR: + ldx FIRST_LINE ; 3 start with the first line in this object +DrawLoop: + lda LineStartPoint,X; 4+ get X0,Y0 + tay ; 2 +_0E_or_10_1: + lda XCoord0_0E,Y ; 4+ the instructions here are modified to load from + sta XSTART ; 3 the appropriate set of X/Y coordinate tables +_0F_or_11_1: + lda YCoord0_0F,Y ; 4+ + sta YSTART ; 3 + lda LineEndPoint,X ; 4+ get X1,Y1 + tay ; 2 +_0E_or_10_2: + lda XCoord0_0E,Y ; 4+ + sta XEND ; 3 +_0F_or_11_2: + lda YCoord0_0F,Y ; 4+ + sta YEND ; 3 + stx LINE_INDEX ; 3 save this off + +; Prep the line draw code. We need to compute deltaX/deltaY, and set a register +; increment / decrement / no-op instruction depending on which way the line is +; going. + + lda XSTART ; 3 compute delta X + sec ; 2 + sbc XEND ; 3 + bcs L1A2F ; 2+ left to right + eor #$ff ; 2 right to left; invert value + adc #$01 ; 2 + ldy #OpINX ; 2 + bne GotDeltaX ; 3 + +L1A2F: + beq IsVertical ; 2+ branch if deltaX=0 + ldy #OpDEX ; 2 + bne GotDeltaX ; 3 + +IsVertical: + ldy #OpNOP ; 2 fully vertical, use no-op +GotDeltaX: + sta DELTA_X ; 3 + sty _InxDexNop1 ; 4 + sty _InxDexNop2 ; 4 + lda YSTART ; 3 compute delta Y + sec ; 2 + sbc YEND ; 3 + bcs L1A4E ; 2+ end < start, we're good + eor #$ff ; 2 invert value + adc #$01 ; 2 + ldy #OpINY ; 2 + bne GotDeltaY ; 3 + +L1A4E: + beq IsHorizontal ; 2+ branch if deltaY=0 + ldy #OpDEY ; 2 + bne GotDeltaY ; 3 + +IsHorizontal: + ldy #OpNOP ; 2 fully horizontal, use no-op +GotDeltaY: + sta DELTA_Y ; 3 + sty _InyDeyNop1 ; 4 + sty _InyDeyNop2 ; 4 + ldx XSTART ; 3 + ldy YSTART ; 3 + lda #$00 ; 2 + sta LINE_ADJ ; 3 + lda DELTA_X ; 3 + cmp DELTA_Y ; 3 + bcs HorizDomLine ; 2+ + +; Line draw: vertically dominant (move vertically every step) +; +; On entry: X=xpos, Y=ypos + +VertDomLine: + cpy YEND ; 3 + beq LineDone ; 2+ +_InyDeyNop1: + nop ; 2 self-mod INY/DEY/NOP + lda YTableLo,Y ; 4+ new line, update Y position + sta HPTR ; 3 + lda YTableHi,Y ; 4+ + ora HPAGE ; 3 + sta HPTR+1 ; 3 + lda LINE_ADJ ; 3 Bresenham update + clc ; 2 + adc DELTA_X ; 3 + cmp DELTA_Y ; 3 + bcs NewColumn ; 2+ + sta LINE_ADJ ; 3 + bcc SameColumn ; 3 + +NewColumn: + sbc DELTA_Y ; 3 + sta LINE_ADJ ; 3 +_InxDexNop1: + nop ;2 self-mod INX/DEX/NOP +SameColumn: + sty YSAVE ; 3 + ldy Div7Tab,X ; 4+ XOR-draw the point + lda (HPTR),Y ; 5+ + eor HiResBitTab,X ; 4+ + sta (HPTR),Y ; 6 + ldy YSAVE ; 3 + jmp VertDomLine ; 3 + +LineDone: + ldx LINE_INDEX ; 3 + inx ; 2 + cpx LAST_LINE ; 3 reached end? + beq DrawDone ; 2+ + jmp DrawLoop ; 3 + +DrawDone: + rts ; 6 + +; Line draw: horizontally dominant (move horizontally every step) +; +; On entry: X=xpos, Y=ypos + +HorizDomLine: + lda YTableLo,Y ; 4+ set up hi-res pointer + sta HPTR ; 3 + lda YTableHi,Y ; 4+ + ora HPAGE ; 3 + sta HPTR+1 ; 3 +HorzLoop: + cpx XEND ; 3 X at end? + beq LineDone ; 2+ yes, finish +_InxDexNop2: + nop ; 2 + lda LINE_ADJ ; 3 Bresenham update + clc ; 2 + adc DELTA_Y ; 3 + cmp DELTA_X ; 3 + bcs NewRow ; 2+ + sta LINE_ADJ ; 3 + bcc SameRow ; 3 + +NewRow: + sbc DELTA_X ; 3 + sta LINE_ADJ ; 3 +_InyDeyNop2: + nop ; 2 + lda YTableLo,Y ; 4+ update Y position + sta HPTR ; 3 + lda YTableHi,Y ; 4+ + ora HPAGE ; 3 + sta HPTR+1 ; 3 +SameRow: + sty YSAVE ; 3 + ldy Div7Tab,X ; 4+ XOR-draw the point + lda (HPTR),Y ; 5+ + eor HiResBitTab,X ; 4+ + sta (HPTR),Y ; 6 + ldy YSAVE ; 3 + jmp HorzLoop ; 3 + + ; Draw code calls here. Since we're configured for XOR mode, this just jumps to + ; the exclusive-or version. If we were configured for OR mode, this would be + ; LDX $45 / LDA $0B00,X instead of a JMP. +DrawLineList: + jmp DrawLineListEOR ; 3 + + ; + ; Unused OR-mode implementation follows. + ; + ; The code is substantially similar to the exclusive-or version, so it has not + ; been annotated. + ; +.byte $00,$0b ; ? + + tay ; 2 + lda XCoord0_0E,Y ; 4+ + sta XSTART ; 3 + lda YCoord0_0F,Y ; 4+ + sta YSTART ; 3 + lda LineEndPoint,X ; 4+ + tay ; 2 + lda XCoord0_0E,Y ; 4+ + sta XEND ; 3 + lda YCoord0_0F,Y ; 4+ + sta YEND ; 3 + stx LINE_INDEX ; 3 + lda XSTART ; 3 + sec ; 2 + sbc XEND ; 3 + bcs L1B1A ; 2+ + eor #$ff ; 2 + adc #$01 ; 2 + ldy #$e8 ; 2 + bne L1B22 ; 3 + +L1B1A: + beq L1B20 ; 2+ + ldy #$ca ; 2 + bne L1B22 ; 3 + +L1B20: + ldy #$ea ; 2 +L1B22: + sta DELTA_X ; 3 + sty L1B79 ; 4 + sty L1BA6 ; 4 + lda YSTART ; 3 + sec ; 2 + sbc YEND ; 3 + bcs L1B39 ; 2+ + eor #$ff ; 2 + adc #$01 ; 2 + ldy #$c8 ; 2 + bne L1B41 ; 3 + +L1B39: + beq L1B3F ; 2+ + ldy #$88 ; 2 + bne L1B41 ; 3 + +L1B3F: + ldy #$ea ; 2 +L1B41: + sta DELTA_Y ; 3 + sty L1B5B ; 4 + sty L1BB8 ; 4 + ldx XSTART ; 3 + ldy YSTART ; 3 + lda #$00 ; 2 + sta LINE_ADJ ; 3 + lda DELTA_X ; 3 + cmp DELTA_Y ; 3 + bcs L1B96 ; 2+ +L1B57: + cpy YEND ; 3 + beq L1B8B ; 2+ +L1B5B: + nop ; 2 + lda YTableLo,Y ; 4+ + sta HPTR ; 3 + lda YTableHi,Y ; 4+ + ora HPAGE ; 3 + sta HPTR+1 ; 3 + lda LINE_ADJ ; 3 + clc ; 2 + adc DELTA_X ; 3 + cmp DELTA_Y ; 3 + bcs L1B75 ; 2+ + sta LINE_ADJ ; 3 + bcc L1B7A ; 3 + +L1B75: + sbc DELTA_Y ; 3 + sta LINE_ADJ ; 3 +L1B79: + nop ; 2 +L1B7A: + sty YSAVE ; 3 + ldy Div7Tab,X ; 4+ + lda (HPTR),Y ; 5+ + ora HiResBitTab,X ; 4+ + sta (HPTR),Y ; 6 + ldy YSAVE ; 3 + jmp L1B57 ; 3 + +L1B8B: + ldx LINE_INDEX ; 3 + inx ; 2 + cpx LAST_LINE ; 3 + beq L1B95 ; 2+ + jmp DrawLineList+2 ; 3 + +L1B95: + rts ; 6 + +L1B96: + lda YTableLo,Y ; 4+ + sta HPTR ; 3 + lda YTableHi,Y ; 4+ + ora HPAGE ; 3 + sta HPTR+1 ; 3 +L1BA2: + cpx XEND ; 3 + beq L1B8B ; 2+ +L1BA6: + nop ; 2 + lda LINE_ADJ ; 3 + clc ; 2 + adc DELTA_Y ; 3 + cmp DELTA_X ; 3 + bcs L1BB4 ; 2+ + sta LINE_ADJ ; 3 + bcc L1BC5 ; 3 + +L1BB4: + sbc DELTA_X ; 3 + sta LINE_ADJ ; 3 +L1BB8: + nop ; 2 + lda YTableLo,Y ; 4+ + sta HPTR ; 3 + lda YTableHi,Y ; 4+ + ora HPAGE ; 3 + sta HPTR+1 ; 3 +L1BC5: + sty YSAVE ; 3 + ldy Div7Tab,X ; 4+ + lda (HPTR),Y ; 5+ + ora HiResBitTab,X ; 4+ + sta (HPTR),Y ; 6 + ldy YSAVE ; 3 + jmp L1BA2 ; 3 + +; +; Unreferenced function that copies untransformed mesh X/Y values into the +; screen-coordinate buffers. Could be used for a static 2D effect, like a +; background that doesn't move. +; + ldx FIRST_LINE ; 3 +UnusedCopyLoop: + lda ShapeXCoords,X ; 4+ + clc ; 2 + adc YSTART ; 3 +_unused_mod0: + sta XCoord0_0E,X ; 5 + lda XEND ; 3 + sec ; 2 + sbc ShapeYCoords,X ; 4+ +_unused_mod1: + sta YCoord0_0F,X ; 5 + inx ; 2 + cpx LAST_LINE ; 3 + bne UnusedCopyLoop ; 2+ + rts ; 6 + + ; Current hi-res page. + ; + ; $00 = draw page 1, show page 2 + ; $FF = draw page 2, show page 1 +CurPage: .byte $ff + + ; + ; Switch to the other hi-res page. + ; +SwapPage: + lda CurPage ; 4 + eor #$ff ; 2 flip to other page + sta CurPage ; 4 + beq DrawOnPage1 ; 3+ + sta TXTPAGE1 ; 4 draw on page 2, show page 1 + lda #$40 ; 2 + ldx #>XCoord1_10 ; 2 + ldy #>YCoord1_11 ; 2 + bne L1C0F ; 3 + +DrawOnPage1: + sta TXTPAGE2 ; 4 draw on page 1, show page 2 + lda #$20 ; 2 + ldx #>XCoord0_0E ; 2 + ldy #>YCoord0_0F ; 2 + + ; Save the hi-res page, and modify the instructions that read from or write data + ; to the transformed point arrays. +L1C0F: + sta HPAGE ; 3 + stx _0E_or_10_1+2 ; 4 + stx _0E_or_10_2+2 ; 4 + stx _0E_or_10_3+2 ; 4 + stx _unused_mod0+2 ; 4 + sty _0F_or_11_1+2 ; 4 + sty _0F_or_11_2+2 ; 4 + sty _0F_or_11_3+2 ; 4 + sty _unused_mod1+2 ; 4 + rts ; 6 + +; Unreferenced junk (12 bytes) +.byte $00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00 + +; Coordinate transformation function. Transforms all points in a single object. +; +; On entry: +; 1c = scale (00-0f) +; 1d = xc (00-ff) +; 1e = yc (00-bf) +; 1f = zrot (00-1b) +; 3c = yrot (00-1b) +; 3d = xrot (00-1b) +; 45 = index of first point to transform +; 46 = index of last point to transform +; +; Rotation values greater than $1B, and scale factors greater than $0F, disable +; the calculation. This has the same effect as a rotation value of 0 or a scale +; of 15, but is more efficient, because this uses self-modifying code to skip +; the computation entirely. +; + +; Clear variables +xc = $19 ; transformed X coordinate +yc = $1a ; transformed Y coordinate +zc = $1b ; transformed Z coordinate +scale = $1c ; $00-0F, where $0F is full size +xposn = $1d ; X coordinate (0-255) +yposn = $1e ; Y coordinate (0-191) +zrot = $1f ; Z rotation ($00-1B) +yrot = $3c ; Y rotation ($00-1B) +xrot = $3d ; X rotation ($00-1B) +rot_tmp = $3f +out_index = $43 +first_point = $45 +last_point = $46 + +CompTransform: + ldx first_point ; 3 get first point index; this stays in X for a while + + ; Configure Z rotation. + + ldy zrot ; 3 + cpy #$1c ; 2 valid rotation value? + bcc ConfigZrot ; 2+ yes, configure + lda #DoTranslate ; 2 + sta _BeforeScale+2 ; 4 + bne TransformLoop ; 4 + +SetScale: + lda #DoScale ; 2 + sta _BeforeScale+2 ; 4 + lda ScaleIndexLo,Y ; 4+ $00, $10, $20, ... $F0 + sta _scaleLX+1 ; 4 + sta _scaleLY+1 ; 4 + lda ScaleIndexHi,Y ; 4+ $00, $01, $02, ... $0F + sta _scaleHX+1 ; 4 + sta _scaleHY+1 ; 4 + + + ; + ; Now that we've got the code modified, perform the computation for all points + ; in the object. + ; +TransformLoop: + lda ShapeXCoords,X ; 4+ + sta xc ; 3 + lda ShapeYCoords,X ; 4+ + sta yc ; 3 + lda ShapeZCoords,X ; 4+ + sta zc ; 3 + stx out_index ; 3 save for later +_BeforeZrot: + jmp DoZrot ; 3 + +DoZrot: + lda xc ; 3 rotating about Z, so we need to update X/Y coords + and #$0f ; 2 split X/Y into nibbles + sta rot_tmp ; 3 + lda xc ; 3 + and #$f0 ; 2 + sta rot_tmp+1 ; 3 + lda yc ; 3 + and #$0f ; 2 + sta rot_tmp+2 ; 3 + lda yc ; 3 + and #$f0 ; 2 + sta rot_tmp+3 ; 3 + ldy rot_tmp ; 3 transform X coord + ldx rot_tmp+1 ; 3 XC = X * cos(theta) - Y * sin(theta) +_zrotLC1: + lda RotTabLo,Y ; 4+ + clc ; 2 +_zrotHC1: + adc RotTabHi,X ; 4+ + ldy rot_tmp+2 ; 3 + ldx rot_tmp+3 ; 3 + sec ; 2 +_zrotLS1: + sbc RotTabLo,Y ; 4+ + sec ; 2 +_zrotHS1: + sbc RotTabHi,X ; 4+ + sta xc ; 3 save updated coord +_zrotLC2: + lda RotTabLo,Y ; 4+ transform Y coord + clc ; 2 YC = Y * cos(theta) + X * sin(theta) +_zrotHC2: + adc RotTabHi,X ; 4+ + ldy rot_tmp ; 3 + ldx rot_tmp+1 ; 3 + clc ; 2 +_zrotLS2: + adc RotTabLo,Y ; 4+ + clc ; 2 +_zrotHS2: + adc RotTabHi,X ; 4+ + sta yc ; 3 save updated coord +_BeforeYrot: + jmp DoYrot ; 3 + +DoYrot: + lda xc ; 3 rotating about Y, so update X/Z + and #$0f ; 2 + sta rot_tmp ; 3 + lda xc ; 3 + and #$f0 ; 2 + sta rot_tmp+1 ; 3 + lda zc ; 3 + and #$0f ; 2 + sta rot_tmp+2 ; 3 + lda zc ; 3 + and #$f0 ; 2 + sta rot_tmp+3 ; 3 + ldy rot_tmp ; 3 + ldx rot_tmp+1 ; 3 +_yrotLC1: + lda RotTabLo,Y ; 4+ + clc ; 2 +_yrotHC1: + adc RotTabHi,X ; 4+ + ldy rot_tmp+2 ; 3 + ldx rot_tmp+3 ; 3 + sec ; 2 +_yrotLS1: + sbc RotTabLo,Y ; 4+ + sec ; 2 +_yrotHS1: + sbc RotTabHi,X ; 4+ + sta xc ; 3 +_yrotLC2: + lda RotTabLo,Y ; 4+ + clc ; 2 +_yrotHC2: + adc RotTabHi,X ; 4+ + ldy rot_tmp ; 3 + ldx rot_tmp+1 ; 3 + clc ; 2 +_yrotLS2: + adc RotTabLo,Y ; 4+ + clc ; 2 +_yrotHS2: + adc RotTabHi,X ; 4+ + sta zc ; 3 +_BeforeXrot: + jmp DoXrot ; 3 + +DoXrot: + lda zc ; 3 rotating about X, so update Z/Y + and #$0f ; 2 + sta rot_tmp ; 3 + lda zc ; 3 + and #$f0 ; 2 + sta rot_tmp+1 ; 3 + lda yc ; 3 + and #$0f ; 2 + sta rot_tmp+2 ; 3 + lda yc ; 3 + and #$f0 ; 2 + sta rot_tmp+3 ; 3 + ldy rot_tmp ; 3 + ldx rot_tmp+1 ; 3 +_xrotLC1: + lda RotTabLo,Y ; 4+ + clc ; 2 +_xrotHC1: + adc RotTabHi,X ; 4+ + ldy rot_tmp+2 ; 3 + ldx rot_tmp+3 ; 3 + sec ; 2 +_xrotLS1: + sbc RotTabLo,Y ; 4+ + sec ; 2 +_xrotHS1: + sbc RotTabHi,X ; 4+ + sta zc ; 3 +_xrotLC2: + lda RotTabLo,Y ; 4+ + clc ; 2 +_xrotHC2: + adc RotTabHi,X ; 4+ + ldy rot_tmp ; 3 + ldx rot_tmp+1 ; 3 + clc ; 2 +_xrotLS2: + adc RotTabLo,Y ; 4+ + clc ; 2 +_xrotHS2: + adc RotTabHi,X ; 4+ + sta yc ; 3 +_BeforeScale: + jmp DoScale ; 3 + + + ; Apply scaling. Traditionally this is applied before rotation. +DoScale: + lda xc ; 3 scale the X coordinate + and #$f0 ; 2 + tax ; 2 + lda xc ; 3 + and #$0f ; 2 + tay ; 2 +_scaleLX: + lda ScaleTabLo,Y ; 4+ + clc ; 2 +_scaleHX: + adc ScaleTabHi,X ; 4+ + sta xc ; 3 + lda yc ; 3 scale the Y coordinate + and #$f0 ; 2 + tax ; 2 + lda yc ; 3 + and #$0f ; 2 + tay ; 2 +_scaleLY: + lda ScaleTabLo,Y ; 4+ + clc ; 2 +_scaleHY: + adc ScaleTabHi,X ; 4+ + sta yc ; 3 + + ; + ; Apply translation. + ; + ; This is the final step, so the result is written to the transformed-point + ; arrays. + ; +DoTranslate: + ldx out_index ; 3 + lda xc ; 3 + clc ; 2 + adc xposn ; 3 object center in screen coordinates +_0E_or_10_3: + sta XCoord0_0E,X ; 5 + lda yposn ; 3 + sec ; 2 + sbc yc ; 3 +_0F_or_11_3: + sta YCoord0_0F,X ; 5 + inx ; 2 + cpx last_point ; 3 done? + beq TransformDone ; 2+ yes, bail + jmp TransformLoop ; 3 + +TransformDone: + rts ; 6 + +SavedShapeIndex: + .byte $ad ;holds shape index while we work + +;******************************************************************************* +; CRUNCH/CRNCH% entry point * +; * +; For each object, do what CODE%(n) tells us to: * +; * +; 0 - do nothing * +; 1 - transform and draw * +; 2 - erase, transform, draw * +; 3 - erase * +;******************************************************************************* + +;FIRST_LINE = $45 +;LAST_LINE = $46 + +CRUNCH: +; jsr Setup ; 6 find Applesoft arrays + + ;============================== + ; First pass: erase old shapes + ;============================== + + lda NumObjects ; 4 number of defined objects +; asl ; 2 * 2 + tax ; 2 use as index +ShapeLoop: +; dex ; 2 + dex ; 2 + bmi Transform ; 2+ done +_codeAR1: + lda CODE_arr,X ; 4+ + cmp #$02 ; 2 2 or 3? + bcc ShapeLoop ; 2+ no, move on + stx SavedShapeIndex ; 4 +; txa ; 2 +; lsr ; 2 +; tax ; 2 + lda FirstLineIndex,X; 4+ + sta FIRST_LINE ; 3 + lda LastLineIndex,X ; 4+ + sta LAST_LINE ; 3 + cmp FIRST_LINE ; 3 is number of lines <= 0? + bcc NoLines1 ; 2+ + beq NoLines1 ; 2+ yes, skip draw + jsr DrawLineListEOR ; 6 erase with EOR version, regardless of config +NoLines1: + ldx SavedShapeIndex ; 4 + bpl ShapeLoop ; 3 ...always + + ;=============================== + ; Second pass: transform shapes + ;=============================== +Transform: + lda NumObjects ; 4 +; asl ; 2 + tax ; 2 +TransLoop: +; dex ; 2 + dex ; 2 + bmi DrawNew ; 2+ +_codeAR2: + lda CODE_arr,X ; 4+ + beq TransLoop ; 2+ is it zero or three? + cmp #$03 ; 2 + beq TransLoop ; 2+ yes, we only draw on 1 or 2 + + ; Extract the scale, X/Y, and rotation values out of the arrays and copy them to + ; zero-page locations. + +_scaleAR: + lda SCALE_arr,X ; 4+ + sta scale ; 3 +_xAR: + lda X_arr,X ; 4+ + sta xposn ; 3 +_yAR: + lda Y_arr,X ; 4+ + sta yposn ; 3 +_zrotAR: + lda ZROT_arr,X ; 4+ + sta zrot ; 3 +_yrotAR: + lda YROT_arr,X ; 4+ + sta yrot ; 3 +_xrotAR: + lda XROT_arr,X ; 4+ + sta xrot ; 3 + stx SavedShapeIndex ; 4 save this off +; txa ; 2 +; lsr ; 2 convert to 1x index +; tax ; 2 + lda FirstPointIndex,X; 4+ + sta FIRST_LINE ; 3 (actually first_point) + lda LastPointIndex,X; 4+ + sta LAST_LINE ; 3 + cmp FIRST_LINE ; 3 is number of points <= 0? + bcc NoPoints ; 2+ + beq NoPoints ; 2+ yes, skip transform + jsr CompTransform ; 6 transform all points +NoPoints: + ldx SavedShapeIndex ; 4 + lda xc ; 3 + clc ; 2 + adc xposn ; 3 +_sxAR: + sta SX_arr,X ; 5 + lda yposn ; 3 + sec ; 2 + sbc yc ; 3 +_syAR: + sta SY_arr,X ; 5 + jmp TransLoop ; 3 + + ;============================= + ; Third pass: draw shapes + ;============================= + +DrawNew: + lda NumObjects ; 4 +; asl ; 2 + tax ; 2 +L1ECE: +; dex ; 2 + dex ; 2 + bmi L1EF9 ; 2+ +_codeAR3: + lda CODE_arr,X ; 4+ is it 0 or 3? + beq L1ECE ; 2+ + cmp #$03 ; 2 + beq L1ECE ; 2+ yup, no draw + stx SavedShapeIndex ; 4 save index +; txa ; 2 +; lsr ; 2 convert it back to 1x index +; tax ; 2 + lda FirstLineIndex,X; 4+ draw all the lines in the shape + sta FIRST_LINE ; 3 + lda LastLineIndex,X ; 4+ + sta LAST_LINE ; 3 + cmp FIRST_LINE ; 3 is number of lines <= 0? + bcc NoLines2 ; 2+ + beq NoLines2 ; 2+ yes, skip draw + jsr DrawLineList ; 6 draw all lines +NoLines2: + ldx SavedShapeIndex ; 4 + bpl L1ECE ; 3 ...always + +L1EF9: + jmp SwapPage ; 3 + + +;******************************************************************************* +; RESET entry point * +; * +; Zeroes out the CODE% array, erases both hi-res screens, and enables display * +; of the primary hi-res page. * +;******************************************************************************* + +RESET: +; jsr Setup ; 6 sets A=0, Y=$1E + lda #$0 + ldy #$F +_codeAR4: + sta CODE_arr,y ; 5 zero out CODE% + dey ; 2 + bpl _codeAR4 ; 3+ + jsr CLEAR ; 6 + jsr SwapPage ; 6 + jsr SwapPage ; 6 + rts ; 6 + +;******************************************************************************* +; CLEAR/CLR% entry point * +; * +; Clears both hi-res pages. * +;******************************************************************************* +; Clear variables + +ptr1 = $1a +ptr2 = $1c + +CLEAR: + lda #$20 ; 2 hi-res page 1 + sta ptr1+1 ; 3 + lda #$40 ; 2 hi-res page 2 + sta ptr2+1 ; 3 + ldy #$00 ; 2 + sty ptr1 ; 3 + sty ptr2 ; 3 +L1F1D: + tya ; 2 +L1F1E: + sta (ptr1),Y ; 6 erase both pages + sta (ptr2),Y ; 6 + iny ; 2 + bne L1F1E ; 2+ + inc ptr1+1 ; 5 + inc ptr2+1 ; 5 + lda ptr1+1 ; 3 (could hold counter in X-reg) + and #$3f ; 2 + bne L1F1D ; 2+ + +;******************************************************************************* +; HIRES entry point * +; * +; Displays primary hi-res page. * +;******************************************************************************* +HI_RES: + sta HIRES ; 4 + sta MIXCLR ; 4 + sta TXTCLR ; 4 + rts ; 6 + +; Locate Applesoft arrays. This assumes the arrays are declared first, and in +; the correct order, so that the start can be found at a fixed offset from the +; BASIC array table pointer. +; +; 1 DIM CODE%(15), X%(15), Y%(15), SCALE%(15), XROT%(15), YROT%(15), ZROT%(15), +; SX%(15), SY%(15) +; +; If you generate the module for Integer BASIC, this is the entry point for +; MISSILE (7993). + + +CODE_arr: + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +X_arr: + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +Y_arr: + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +XROT_arr: + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +YROT_arr: + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +ZROT_arr: + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +SCALE_arr: + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +SX_arr: + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +SY_arr: + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + + + +.if 0 + +Setup: + lda BAS_ARYTAB ; 3 CODE% is at +$0008 + clc ; 2 + adc #$08 ; 2 + tax ; 2 + lda BAS_ARYTAB+1 ; 3 + adc #$00 ; 2 + stx _codeAR1+1 ; 4 + sta _codeAR1+2 ; 4 + stx _codeAR2+1 ; 4 + sta _codeAR2+2 ; 4 + stx _codeAR3+1 ; 4 + sta _codeAR3+2 ; 4 + stx _codeAR4+1 ; 4 + sta _codeAR4+2 ; 4 + lda BAS_ARYTAB ; 3 X% is at +$002F + clc ; 2 + adc #$2f ; 2 + tax ; 2 + lda BAS_ARYTAB+1 ; 3 + adc #$00 ; 2 + stx _xAR+1 ; 4 + sta _xAR+2 ; 4 + lda BAS_ARYTAB ; 3 Y% is at +$0056 + clc ; 2 + adc #$56 ; 2 + tax ; 2 + lda BAS_ARYTAB+1 ; 3 + adc #$00 ; 2 + stx _yAR+1 ; 4 + sta _yAR+2 ; 4 + lda BAS_ARYTAB ; 3 SCALE% is at + $007D + clc ; 2 + adc #$7d ; 2 + tax ; 2 + lda BAS_ARYTAB+1 ; 3 + adc #$00 ; 2 + stx _scaleAR+1 ; 4 + sta _scaleAR+2 ; 4 + lda BAS_ARYTAB ; 3 XROT% is at +$00A4 + clc ; 2 + adc #$a4 ; 2 + tax ; 2 + lda BAS_ARYTAB+1 ; 3 + adc #$00 ; 2 + stx _xrotAR+1 ; 4 + sta _xrotAR+2 ; 4 + lda BAS_ARYTAB ; 3 YROT% is at +$00CB + clc ; 2 + adc #$cb ; 2 + tax ; 2 + lda BAS_ARYTAB+1 ; 3 + adc #$00 ; 2 + stx _yrotAR+1 ; 4 + sta _yrotAR+2 ; 4 + lda BAS_ARYTAB ; 3 ZROT% is at +$00F2 + clc ; 2 + adc #$f2 ; 2 + tax ; 2 + lda BAS_ARYTAB+1 ; 3 + adc #$00 ; 2 + stx _zrotAR+1 ; 4 + sta _zrotAR+2 ; 4 + lda BAS_ARYTAB ; 3 SX% is at +$0119 + clc ; 2 + adc #$19 ; 2 + tax ; 2 + lda BAS_ARYTAB+1 ; 3 + adc #$01 ; 2 + stx _sxAR+1 ; 4 + sta _sxAR+2 ; 4 + lda BAS_ARYTAB ; 3 SY% is at +$0140 + clc ; 2 + adc #$40 ; 2 + tax ; 2 + lda BAS_ARYTAB+1 ; 3 + adc #$01 ; 2 + stx _syAR+1 ; 4 + sta _syAR+2 ; 4 + ldy #$1e ; 2 Set A and Y for RESET + lda #$00 ; 2 + rts ; 6 +.endif + + ; Junk; pads binary to end of page. +.align $0100 + diff --git a/graphics/hgr/budge3d/asm_example/zp.inc b/graphics/hgr/budge3d/asm_example/zp.inc new file mode 100644 index 00000000..83dfdf87 --- /dev/null +++ b/graphics/hgr/budge3d/asm_example/zp.inc @@ -0,0 +1,33 @@ +OpDEY = $88 ; DEY opcode +OpINY = $c8 ; INY opcode +OpDEX = $ca ; DEX opcode +OpINX = $e8 ; INX opcode +OpNOP = $ea ; NOP opcode + +; zero page addresses + +MON_WNDLEFT = $20 ; left column of scroll window +MON_WNDWDTH = $21 ; width of scroll window +MON_WNDTOP = $22 ; top of scroll window +MON_WNDBTM = $23 ; bottom of scroll window +MON_CH = $24 ; cursor horizontal displacement +MON_CV = $25 ; cursor vertical displacement +MON_CSWL = $36 ; character output hook (lo) +MON_CSWH = $37 ; character output hook (hi) +BAS_ARYTAB = $6b ; pointer to start of Applesoft array space (2b) + +; Clear variables + +HPTR = $06 +HPAGE = $18 ; hi-res page ($20 or $40) +XSTART = $1c +YSTART = $1d +XEND = $1e +YEND = $1f +DELTA_X = $3c +DELTA_Y = $3d +LINE_ADJ = $3e +LINE_INDEX = $43 +YSAVE = $00 +FIRST_LINE = $45 +LAST_LINE = $46 diff --git a/graphics/hgr/budge3d/basic_example/Makefile b/graphics/hgr/budge3d/basic_example/Makefile new file mode 100644 index 00000000..892f8f41 --- /dev/null +++ b/graphics/hgr/budge3d/basic_example/Makefile @@ -0,0 +1,40 @@ +include ../../../../Makefile.inc + +DOS33 = ../../../../utils/dos33fs-utils/dos33 +TOKENIZE = ../../../../utils/asoft_basic-utils/tokenize_asoft +LINKER_SCRIPTS = ../../../../linker_scripts +EMPTY_DISK = ../../../../empty_disk + +all: budge3d.dsk + +budge3d.dsk: HELLO TEST SHIP_CUBE + cp $(EMPTY_DISK)/empty.dsk budge3d.dsk + $(DOS33) -y budge3d.dsk SAVE A HELLO + $(DOS33) -y budge3d.dsk SAVE A TEST + $(DOS33) -y budge3d.dsk BSAVE -a 0x300 SHIP_CUBE + + +### + +HELLO: hello.bas + $(TOKENIZE) < hello.bas > HELLO + +### + +TEST: test.bas + $(TOKENIZE) < test.bas > TEST + +#### + +SHIP_CUBE: ship_cube.o + ld65 -o SHIP_CUBE ship_cube.o -C $(LINKER_SCRIPTS)/apple2_300.inc + +ship_cube.o: ship_cube.s \ + zp.inc hardware.inc \ + shapes.s math_constants.s hgr_tables.s \ + hgr_textgen.s scale_constants.s + ca65 -o ship_cube.o ship_cube.s -l ship_cube.lst + +#### +clean: + rm -f *~ *.o *.lst HELLO SHIP_CUBE TEST diff --git a/graphics/hgr/budge3d/basic_example/hardware.inc b/graphics/hgr/budge3d/basic_example/hardware.inc new file mode 100644 index 00000000..269c4a3c --- /dev/null +++ b/graphics/hgr/budge3d/basic_example/hardware.inc @@ -0,0 +1,17 @@ +CODE_arr = $6008 ; CODE array (dummy address) +X_arr = $602f ; X array (dummy address) +Y_arr = $6056 ; Y array (dummy address) +SCALE_arr = $607d ; SCALE array (dummy address) +XROT_arr = $60a4 ; XROT array (dummy address) +YROT_arr = $60cb ; YROT array (dummy address) +ZROT_arr = $60f2 ; ZROT array (dummy address) +SX_arr = $6119 ; SX array (dummy address) +SY_arr = $6140 ; SY array (dummy address) + +; soft switches +TXTCLR = $c050 ; RW display graphics +MIXCLR = $c052 ; RW display full screen +TXTPAGE1 = $c054 ; RW display page 1 +TXTPAGE2 = $c055 ; RW display page 2 (or read/write aux mem) +HIRES = $c057 ; RW display hi-res graphics + diff --git a/graphics/hgr/budge3d/basic_example/hello.bas b/graphics/hgr/budge3d/basic_example/hello.bas new file mode 100644 index 00000000..8ef36c79 --- /dev/null +++ b/graphics/hgr/budge3d/basic_example/hello.bas @@ -0,0 +1,3 @@ +10 POKE 24576,0: POKE 103,1: POKE 104,96: POKE 175,1: POKE 176,96 +30 PRINT CHR$ (4)"RUN TEST" + diff --git a/graphics/hgr/budge3d/basic_example/hgr_tables.s b/graphics/hgr/budge3d/basic_example/hgr_tables.s new file mode 100644 index 00000000..3fa45f1d --- /dev/null +++ b/graphics/hgr/budge3d/basic_example/hgr_tables.s @@ -0,0 +1,102 @@ +; +; Divide-by-7 table. Used to divide the X coordinate (0-255) by 7, yielding a +; byte offset for the hi-res screen column. +; +Div7Tab: ; 1400 + .byte $00,$00,$00,$00,$00,$00,$00,$01,$01,$01,$01,$01,$01,$01,$02,$02 + .byte $02,$02,$02,$02,$02,$03,$03,$03,$03,$03,$03,$03,$04,$04,$04,$04 + .byte $04,$04,$04,$05,$05,$05,$05,$05,$05,$05,$06,$06,$06,$06,$06,$06 + .byte $06,$07,$07,$07,$07,$07,$07,$07,$08,$08,$08,$08,$08,$08,$08,$09 + .byte $09,$09,$09,$09,$09,$09,$0a,$0a,$0a,$0a,$0a,$0a,$0a,$0b,$0b,$0b + .byte $0b,$0b,$0b,$0b,$0c,$0c,$0c,$0c,$0c,$0c,$0c,$0d,$0d,$0d,$0d,$0d + .byte $0d,$0d,$0e,$0e,$0e,$0e,$0e,$0e,$0e,$0f,$0f,$0f,$0f,$0f,$0f,$0f + .byte $10,$10,$10,$10,$10,$10,$10,$11,$11,$11,$11,$11,$11,$11,$12,$12 + .byte $12,$12,$12,$12,$12,$13,$13,$13,$13,$13,$13,$13,$14,$14,$14,$14 + .byte $14,$14,$14,$15,$15,$15,$15,$15,$15,$15,$16,$16,$16,$16,$16,$16 + .byte $16,$17,$17,$17,$17,$17,$17,$17,$18,$18,$18,$18,$18,$18,$18,$19 + .byte $19,$19,$19,$19,$19,$19,$1a,$1a,$1a,$1a,$1a,$1a,$1a,$1b,$1b,$1b + .byte $1b,$1b,$1b,$1b,$1c,$1c,$1c,$1c,$1c,$1c,$1c,$1d,$1d,$1d,$1d,$1d + .byte $1d,$1d,$1e,$1e,$1e,$1e,$1e,$1e,$1e,$1f,$1f,$1f,$1f,$1f,$1f,$1f + .byte $20,$20,$20,$20,$20,$20,$20,$21,$21,$21,$21,$21,$21,$21,$22,$22 + .byte $22,$22,$22,$22,$22,$23,$23,$23,$23,$23,$23,$23,$24,$24,$24,$24 + +; +; Hi-res bit table. Converts the X coordinate (0-255) into a bit position +; within a byte. (Essentially 2 to the power of the remainder of the coordinate +; divided by 7.) +; + +HiResBitTab: ; 1500 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08,$10,$20,$40,$01,$02,$04,$08,$10,$20,$40 + .byte $01,$02,$04,$08 + +; +; Hi-res Y-coordinate lookup table, low byte. Values 0-191 are meaningful, 192- +; 255 are junk. +; + +YTableLo: ; 1600 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$80,$80,$80,$80,$80,$80,$80,$80,$00,$00,$00,$00,$00,$00,$00,$00,$80,$80,$80,$80,$80,$80,$80,$80 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$80,$80,$80,$80,$80,$80,$80,$80,$00,$00,$00,$00,$00,$00,$00,$00,$80,$80,$80,$80,$80,$80,$80,$80 + .byte $28,$28,$28,$28,$28,$28,$28,$28,$a8,$a8,$a8,$a8,$a8,$a8,$a8,$a8,$28,$28,$28,$28,$28,$28,$28,$28,$a8,$a8,$a8,$a8,$a8,$a8,$a8,$a8 + .byte $28,$28,$28,$28,$28,$28,$28,$28,$a8,$a8,$a8,$a8,$a8,$a8,$a8,$a8,$28,$28,$28,$28,$28,$28,$28,$28,$a8,$a8,$a8,$a8,$a8,$a8,$a8,$a8 + .byte $50,$50,$50,$50,$50,$50,$50,$50,$d0,$d0,$d0,$d0,$d0,$d0,$d0,$d0,$50,$50,$50,$50,$50,$50,$50,$50,$d0,$d0,$d0,$d0,$d0,$d0,$d0,$d0 + .byte $50,$50,$50,$50,$50,$50,$50,$50,$d0,$d0,$d0,$d0,$d0,$d0,$d0,$d0,$50,$50,$50,$50,$50,$50,$50,$50,$d0,$d0,$d0,$d0,$d0,$d0,$d0,$d0 + + +.align $0100 ; (64 bytes) + +;============================================== +; Hi-res Y-coordinate lookup table, high byte. +;============================================== + +YTableHi: + .byte $00,$04,$08,$0c,$10,$14,$18,$1c + .byte $00,$04,$08,$0c,$10,$14,$18,$1c + .byte $01,$05,$09,$0d,$11,$15,$19,$1d + .byte $01,$05,$09,$0d,$11,$15,$19,$1d + + .byte $02,$06,$0a,$0e,$12,$16,$1a,$1e + .byte $02,$06,$0a,$0e,$12,$16,$1a,$1e + .byte $03,$07,$0b,$0f,$13,$17,$1b,$1f + .byte $03,$07,$0b,$0f,$13,$17,$1b,$1f + + .byte $00,$04,$08,$0c,$10,$14,$18,$1c + .byte $00,$04,$08,$0c,$10,$14,$18,$1c + .byte $01,$05,$09,$0d,$11,$15,$19,$1d + .byte $01,$05,$09,$0d,$11,$15,$19,$1d + + .byte $02,$06,$0a,$0e,$12,$16,$1a,$1e + .byte $02,$06,$0a,$0e,$12,$16,$1a,$1e + .byte $03,$07,$0b,$0f,$13,$17,$1b,$1f + .byte $03,$07,$0b,$0f,$13,$17,$1b,$1f + + .byte $00,$04,$08,$0c,$10,$14,$18,$1c + .byte $00,$04,$08,$0c,$10,$14,$18,$1c + .byte $01,$05,$09,$0d,$11,$15,$19,$1d + .byte $01,$05,$09,$0d,$11,$15,$19,$1d + + .byte $02,$06,$0a,$0e,$12,$16,$1a,$1e + .byte $02,$06,$0a,$0e,$12,$16,$1a,$1e + .byte $03,$07,$0b,$0f,$13,$17,$1b,$1f + .byte $03,$07,$0b,$0f,$13,$17,$1b,$1f + +.align $0100 ; (64 bytes) + diff --git a/graphics/hgr/budge3d/hgr_textgen.s b/graphics/hgr/budge3d/basic_example/hgr_textgen.s similarity index 100% rename from graphics/hgr/budge3d/hgr_textgen.s rename to graphics/hgr/budge3d/basic_example/hgr_textgen.s diff --git a/graphics/hgr/budge3d/basic_example/math_constants.s b/graphics/hgr/budge3d/basic_example/math_constants.s new file mode 100644 index 00000000..a0e38fa5 --- /dev/null +++ b/graphics/hgr/budge3d/basic_example/math_constants.s @@ -0,0 +1,68 @@ +; +; Math constants for rotation. +; +; To compute X * cos(theta), start by converting theta (0-27) into a table base +; address (using the 28-byte tables RotIndexLo_cos / RotIndexHi_cos). Split the +; X coordinate into nibbles, use the low 4 bits to index into the adjusted +; RotTabLo pointer, and the high 4 bits to index into the adjusted RotTabHi +; pointer. Add the values at those locations together. +; +; This is similar to the way the scale table works. See ScaleTableLo, below, +; for a longer explanation of how the nibbles are used. +; +; As an example, suppose we have a point at (36,56), and we want to rotate it 90 +; degrees (rot=7). We use the RotIndex tables to get the table base addresses: +; sin=$00/$00 ($1200/$1300), cos=$70/$07 ($1270/$1307). We split the +; coordinates into nibbles without shifting ($24,$38 --> $20 $04, $30 $08), and +; use the nibbles as indexes into the tables: +; +; X * cos(theta) = ($1274)+($1327) = $00+$00 = 0 +; Y * sin(theta) = ($1208)+($1330) = $08+$30 = 56 +; X * sin(theta) = ($1204)+($1320) = $04+$20 = 36 +; Y * cos(theta) = ($1278)+($1337) = $00+$00 = 0 +; +; XC = X*cos(theta) - Y*sin(theta) = -56 +; YC = X*sin(theta) + Y*cos(theta) = 36 +; +; which is exactly what we expected (counter-clockwise). +; +; The largest value from the index table is $EE, so that last 17 bytes in each +; table are unused. +; + +RotTabLo: ; 1200 + .byte $00,$01,$02,$03,$04,$05,$06,$07,$08,$09,$0a,$0b,$0c,$0d,$0e,$0f + .byte $00,$01,$02,$03,$04,$05,$06,$07,$08,$09,$0a,$0b,$0c,$0d,$0e,$0f + .byte $00,$01,$02,$03,$04,$05,$05,$06,$07,$08,$09,$0a,$0b,$0c,$0d,$0e + .byte $00,$01,$02,$02,$03,$04,$05,$05,$06,$07,$08,$09,$09,$0a,$0b,$0c + .byte $00,$01,$01,$02,$02,$03,$04,$04,$05,$06,$06,$07,$07,$08,$09,$09 + .byte $00,$00,$01,$01,$02,$02,$03,$03,$03,$04,$04,$05,$05,$06,$06,$07 + .byte $00,$00,$00,$01,$01,$01,$01,$02,$02,$02,$02,$02,$03,$03,$03,$03 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $00,$00,$00,$ff,$ff,$ff,$ff,$fe,$fe,$fe,$fe,$fe,$fd,$fd,$fd,$fd + .byte $00,$00,$ff,$ff,$fe,$fe,$fd,$fd,$fd,$fc,$fc,$fb,$fb,$fa,$fa,$f9 + .byte $00,$ff,$ff,$fe,$fe,$fd,$fc,$fc,$fb,$fa,$fa,$f9,$f9,$f8,$f7,$f7 + .byte $00,$ff,$fe,$fe,$fd,$fc,$fb,$fb,$fa,$f9,$f8,$f7,$f7,$f6,$f5,$f4 + .byte $00,$ff,$fe,$fd,$fc,$fb,$fb,$fa,$f9,$f8,$f7,$f6,$f5,$f4,$f3,$f2 + .byte $00,$ff,$fe,$fd,$fc,$fb,$fb,$fa,$f8,$f7,$f6,$f5,$f4,$f3,$f2,$f1 + .byte $00,$ff,$fe,$fd,$fc,$fb,$fa,$f9,$f8,$f7,$f6,$f5,$f4,$f3,$f2,$f1 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + +RotTabHi: ; 1300 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $10,$10,$0e,$0d,$0a,$07,$04,$00,$fc,$f9,$f6,$f3,$f2,$f0,$f0,$00 + .byte $20,$1f,$1d,$19,$14,$0e,$07,$00,$f9,$f2,$ec,$e7,$e3,$e1,$e0,$00 + .byte $30,$2f,$2b,$26,$1e,$15,$0b,$00,$f5,$eb,$e2,$da,$d5,$d1,$d0,$00 + .byte $40,$3e,$3a,$32,$28,$1c,$0e,$00,$f2,$e4,$d8,$ce,$c6,$c2,$c0,$00 + .byte $50,$4e,$48,$3f,$32,$23,$12,$00,$ee,$dd,$ce,$c1,$b8,$b2,$b0,$00 + .byte $60,$5e,$56,$4b,$3c,$2a,$15,$00,$eb,$d6,$c4,$b5,$aa,$a2,$a0,$00 + .byte $70,$6d,$65,$58,$46,$31,$19,$00,$e7,$cf,$ba,$a8,$9b,$93,$90,$00 + .byte $80,$83,$8d,$9c,$b0,$c8,$e4,$00,$1c,$38,$50,$64,$73,$7d,$80,$00 + .byte $90,$93,$9b,$a8,$ba,$cf,$e7,$00,$19,$31,$46,$58,$65,$6d,$70,$00 + .byte $a0,$a2,$aa,$b5,$c4,$d6,$eb,$00,$15,$2a,$3c,$4b,$56,$5e,$60,$00 + .byte $b0,$b2,$b8,$c1,$ce,$dd,$ee,$00,$12,$23,$32,$3f,$48,$4e,$50,$00 + .byte $c0,$c2,$c6,$ce,$d8,$e4,$f2,$00,$0e,$1c,$28,$32,$3a,$3e,$40,$00 + .byte $d0,$d1,$d5,$da,$e2,$eb,$f5,$00,$0b,$15,$1e,$26,$2b,$2f,$30,$00 + .byte $e0,$e1,$e3,$e7,$ec,$f2,$f9,$00,$07,$0e,$14,$19,$1d,$1f,$20,$00 + .byte $f0,$f0,$f2,$f3,$f6,$f9,$fc,$00,$04,$07,$0a,$0d,$0e,$10,$10,$00 + diff --git a/graphics/hgr/budge3d/basic_example/scale_constants.s b/graphics/hgr/budge3d/basic_example/scale_constants.s new file mode 100644 index 00000000..a6eafba1 --- /dev/null +++ b/graphics/hgr/budge3d/basic_example/scale_constants.s @@ -0,0 +1,113 @@ +; +; Math constants for scaling. +; +; Each table has 16 sets of 16 entries, with one set for each of the 16 possible +; scale values. The values within a set determine how one 4-bit nibble of the +; coordinate is scaled. +; +; Suppose you want to scale the value 100 ($64) by scale factor 8 (a bit over +; half size). We begin by using self-modifying code to select the table +; subsets. This is done in a clever way to avoid shifting. The instructions +; that load from ScaleTabLo are modified to reference $1800, $1810, $1820, and +; so on. The instructions that load from ScaleTabHi reference $1900, $1901, +; $1902, etc. The offset comes from the two 16-byte ScaleIndex tables. For a +; scale factor of 8, we'll be using $1880 and $1908 as the base addresses. +; +; To do the actual scaling, we mask to get the low part of the value ($04) and +; index into ScaleTabLo, at address $1884. We mask the high part of the value +; ($60) and index into ScaleTabHi, at $1968. We add the values there ($02, $36) +; to get $38 = 56, which is just over half size as expected. +; +; This is an approximation, but so is any integer division, and it's done in +; 512+32=544 bytes instead of the 16*256=4096 bytes that you'd need for a fully- +; formed scale. For hi-res graphics it's certainly good enough. +; +; 32 = $20 = ($1880)+($1928) = 18 (.563) +; 40 = $28 = ($1888)+($1928) = 22 (.55) +; 47 = $2F = ($188F)+($1928) = 26 (.553) +; 48 = $30 = ($1880)+($1938) = 27 (.563) +; 100 = $64 = ($1884)+($1968) = 56 (.56) +; + +ScaleTabLo: + .byte $00,$00,$00,$00,$00,$00,$00,$00 + .byte $00,$00,$00,$00,$00,$00,$00,$00 + .byte $00,$00,$00,$00,$00,$00,$00,$00 + .byte $01,$01,$01,$01,$01,$01,$01,$01 + + .byte $00,$00,$00,$00,$00,$00,$01,$01 + .byte $01,$01,$01,$02,$02,$02,$02,$02 + .byte $00,$00,$00,$00,$01,$01,$01,$01 + .byte $02,$02,$02,$02,$03,$03,$03,$03 + + .byte $00,$00,$00,$00,$01,$01,$01,$02 + .byte $02,$02,$03,$03,$03,$04,$04,$04 + .byte $00,$00,$00,$01,$01,$01,$02,$02 + .byte $03,$03,$03,$04,$04,$04,$05,$05 + + .byte $00,$00,$00,$01,$01,$02,$02,$03 + .byte $03,$03,$04,$04,$05,$05,$06,$06 + .byte $00,$00,$01,$01,$02,$02,$03,$03 + .byte $04,$04,$05,$05,$06,$06,$07,$07 + + .byte $00,$00,$01,$01,$02,$02,$03,$03 + .byte $04,$05,$05,$06,$06,$07,$07,$08 + .byte $00,$00,$01,$01,$02,$03,$03,$04 + .byte $05,$05,$06,$06,$07,$08,$08,$09 + + .byte $00,$00,$01,$02,$02,$03,$04,$04 + .byte $05,$06,$06,$07,$08,$08,$09,$0a + .byte $00,$00,$01,$02,$03,$03,$04,$05 + .byte $06,$06,$07,$08,$09,$09,$0a,$0b + + .byte $00,$00,$01,$02,$03,$04,$04,$05 + .byte $06,$07,$08,$08,$09,$0a,$0b,$0c + .byte $00,$00,$01,$02,$03,$04,$05,$06 + .byte $07,$07,$08,$09,$0a,$0b,$0c,$0d + + .byte $00,$00,$01,$02,$03,$04,$05,$06 + .byte $07,$08,$09,$0a,$0b,$0c,$0d,$0e + .byte $00,$01,$02,$03,$04,$05,$06,$07 + .byte $08,$09,$0a,$0b,$0c,$0d,$0e,$0f + +ScaleTabHi: + .byte $00,$00,$00,$00,$00,$00,$00,$00 + .byte $00,$00,$00,$00,$00,$00,$00,$00 + .byte $01,$02,$03,$04,$05,$06,$07,$08 + .byte $09,$0a,$0b,$0c,$0d,$0e,$0f,$10 + + .byte $02,$04,$06,$08,$0a,$0c,$0e,$10 + .byte $12,$14,$16,$18,$1a,$1c,$1e,$20 + .byte $03,$06,$09,$0c,$0f,$12,$15,$18 + .byte $1b,$1e,$21,$24,$27,$2a,$2d,$30 + + .byte $04,$08,$0c,$10,$14,$18,$1c,$20 + .byte $24,$28,$2c,$30,$34,$38,$3c,$40 + .byte $05,$0a,$0f,$14,$19,$1e,$23,$28 + .byte $2d,$32,$37,$3c,$41,$46,$4b,$50 + + .byte $06,$0c,$12,$18,$1e,$24,$2a,$30 + .byte $36,$3c,$42,$48,$4e,$54,$5a,$60 + .byte $07,$0e,$15,$1c,$23,$2a,$31,$38 + .byte $3f,$46,$4d,$54,$5b,$62,$69,$70 + + .byte $f8,$f0,$e8,$e0,$d8,$d0,$c8,$c0 + .byte $b8,$b0,$a8,$a0,$98,$90,$88,$80 + .byte $f9,$f2,$eb,$e4,$dd,$d6,$cf,$c8 + .byte $c1,$ba,$b3,$ac,$a5,$9e,$97,$90 + + .byte $fa,$f4,$ee,$e8,$e2,$dc,$d6,$d0 + .byte $ca,$c4,$be,$b8,$b2,$ac,$a6,$a0 + .byte $fb,$f6,$f1,$ec,$e7,$e2,$dd,$d8 + .byte $d3,$ce,$c9,$c4,$bf,$ba,$b5,$b0 + + .byte $fc,$f8,$f4,$f0,$ec,$e8,$e4,$e0 + .byte $dc,$d8,$d4,$d0,$cc,$c8,$c4,$c0 + .byte $fd,$fa,$f7,$f4,$f1,$ee,$eb,$e8 + .byte $e5,$e2,$df,$dc,$d9,$d6,$d3,$d0 + + .byte $fe,$fc,$fa,$f8,$f6,$f4,$f2,$f0 + .byte $ee,$ec,$ea,$e8,$e6,$e4,$e2,$e0 + .byte $ff,$fe,$fd,$fc,$fb,$fa,$f9,$f8 + .byte $f7,$f6,$f5,$f4,$f3,$f2,$f1,$f0 + diff --git a/graphics/hgr/budge3d/basic_example/shapes.s b/graphics/hgr/budge3d/basic_example/shapes.s new file mode 100644 index 00000000..5968f815 --- /dev/null +++ b/graphics/hgr/budge3d/basic_example/shapes.s @@ -0,0 +1,208 @@ +;=========================================================== +; +; If configured without the HRCG, the module starts here. * +; +; Note that all tables are page-aligned for performance. * +; +;=========================================================== + +NumObjects: .byte 2 ; number of objects + .byte 35 ; number of points (unused) + .byte 41 ; number of lines (unused) +; +; The next five tables represent the data as it was entered into the shape +; editor. There are two shapes. The first (space shuttle) starts at offset 0, +; has 27 points, and 29 lines. The second (cube) starts immediately after the +; first, has 8 points, and 12 lines. +; + +; 3D mesh X coordinates (-60, -57, ...) +ShapeXCoords: + + ; spaceship + + .byte $c4,$c7,$c7,$c7,$c7,$d6,$d6,$d6 ; $00 + .byte $d6,$f1,$f1,$00,$00,$15,$15,$1e ; $08 + .byte $1e,$1e,$1e,$24,$24,$24,$24,$09 ; $10 + .byte $1b,$15,$1e ; $18 + + ; cube + .byte $fb,$05,$05,$fb,$fb ; $1b + .byte $05,$05,$fb ; $20 + + ; junk + + .byte $ec,$fc,$0c,$18,$28,$30,$44,$50,$74,$7c,$05,$fb,$00,$c4,$ca,$ca,$ca,$ca,$d6,$d6,$d6,$d6,$f1,$f1,$00,$00,$15,$15,$1e + .byte $1e,$1e,$1e,$24,$24,$24,$24,$09,$1b,$15,$1e,$d8,$e8,$f8,$08,$18,$28,$9c,$c4,$ec,$14,$3c,$64,$c9,$37,$b5,$4b,$22,$de,$de,$f2,$0e + .byte $22,$22,$de,$de,$f2,$0e,$22,$28,$39,$46,$d8,$c7,$ba,$00,$00,$00,$4d,$4d,$3f,$3f,$b3,$b3,$c1,$c1,$f9,$07,$07,$f9,$11,$ef,$ef,$11 + .byte $08,$f8,$0a,$f6,$19,$e7,$19,$e7,$00,$fa,$06,$00,$00,$fc,$04,$fc,$04,$fa,$06,$f6,$0a,$fc,$04,$f4,$0c,$fa,$06,$fa,$06,$f6,$0a,$f6 + .byte $0a,$f4,$0c,$f4,$0c,$d0,$30,$d0,$30,$d0,$30,$d0,$30,$d0,$30,$d0,$30,$d3,$06,$fc,$1a,$ba,$00,$da,$03,$16,$1a,$b0,$00,$ba,$02,$10 + .byte $34,$1a,$98,$19,$2b,$da,$03,$1b,$ab,$3b,$a0,$a0,$ab,$a4,$01,$df,$82,$d9,$0b,$f2,$0c,$d8,$06,$06,$2b,$7c,$10,$5b,$08,$3f,$19,$16 + .byte $0f,$01,$9c,$19,$23,$0f,$01,$97,$f2,$18,$24,$00,$0c,$c0,$f8,$06,$ed,$2b,$7c,$42,$1a,$ac,$00,$ba,$5c,$06,$f1,$1a,$03,$00,$da,$06 + +; 3D mesh Y coordinates (0, 3, ...) +ShapeYCoords: + ; spaceship + .byte $00,$03,$03,$fd,$fd,$06,$09,$fa + .byte $f7,$09,$f7,$0f,$f1,$24,$dc,$24 + .byte $dc,$09,$f7,$09,$f7,$06,$fa,$00 + .byte $00,$00,$00 + + ; cube + .byte $fb,$fb,$05,$05,$fb + .byte $fb,$05,$05 + + ; garbage + .byte $d0,$e0,$bc,$b0,$c4,$d8,$d0,$e0,$e0,$d0,$0a,$0a,$22,$00,$05,$05,$fb,$fb,$06,$09,$fa,$f7,$09,$f7,$0f,$f1,$24,$dc,$24 + .byte $dc,$09,$f7,$09,$f7,$06,$fa,$00,$00,$00,$00,$20,$20,$20,$20,$20,$20,$e0,$e0,$e0,$e0,$e0,$e0,$10,$10,$fa,$fa,$f4,$f4,$0c,$20,$20 + .byte $0c,$f4,$f4,$0c,$20,$20,$0c,$00,$00,$00,$00,$00,$00,$28,$39,$46,$f9,$07,$07,$f9,$f9,$07,$07,$f9,$4d,$4d,$3f,$3f,$ef,$ef,$11,$11 + .byte $0e,$0e,$1b,$1b,$11,$11,$e7,$e7,$00,$06,$06,$fa,$0a,$0a,$0a,$0a,$0a,$06,$06,$06,$06,$fa,$fa,$06,$06,$06,$06,$fa,$fa,$04,$04,$fc + .byte $fc,$04,$04,$fc,$fc,$0c,$0c,$f4,$f4,$0c,$0c,$f4,$f4,$0c,$0c,$f4,$f4,$06,$00,$4f,$0c,$d0,$d2,$a3,$02,$00,$2c,$0d,$c5,$e4,$e9,$f4 + .byte $04,$06,$00,$a2,$0d,$c1,$00,$00,$00,$20,$4d,$0a,$a9,$ff,$85,$31,$a0,$00,$a5,$24,$c9,$27,$d0,$09,$20,$8e,$fd,$85,$31,$a9,$06,$85 + .byte $24,$b1,$12,$c5,$30,$d0,$13,$e6,$31,$a4,$31,$c0,$10,$b0,$0b,$be,$f0,$00,$e4,$24,$90,$04,$86,$24,$90,$d6,$20,$ed,$fd,$e6,$12,$d0 + +; 3D mesh Z coordinates (0, 3, ...) +ShapeZCoords: + ; spaceship + .byte $00,$03,$fd,$03,$fd,$09,$fa,$09 + .byte $fa,$fa,$fa,$fa,$fa,$fa,$fa,$fa + .byte $fa,$fa,$fa,$fa,$fa,$09,$09,$09 + .byte $09,$1b,$1b + + ; cube + .byte $fb,$fb,$fb,$fb,$05 + .byte $05,$05,$05 + + ; garbage + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$05,$fb,$05,$fb,$09,$fa,$09,$fa,$fa,$fa,$fa,$fa,$fa,$fa,$fa + .byte $fa,$fa,$fa,$fa,$fa,$09,$09,$09,$09,$1e,$1e,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $00,$e2,$e2,$e0,$e0,$e0,$e0,$fd,$de,$c9,$fd,$de,$c9,$fb,$db,$c6,$c9,$c6,$c6,$c9,$c9,$c6,$c6,$c9,$c6,$c6,$c9,$c9,$28,$28,$2a,$2a + .byte $16,$16,$03,$03,$20,$20,$1e,$1e,$5a,$1e,$1e,$1e,$24,$22,$22,$0c,$0c,$12,$12,$10,$10,$0c,$0c,$e8,$e8,$e2,$e2,$e8,$e8,$fa,$fa,$fa + .byte $fa,$e8,$e8,$e8,$e8,$f4,$f4,$f4,$f4,$ee,$ee,$ee,$ee,$00,$00,$00,$00,$89,$f6,$01,$e2,$10,$27,$e8,$03,$64,$00,$0a,$00,$01,$00,$2b + .byte $35,$25,$37,$00,$4c,$45,$08,$2b,$d7,$02,$58,$36,$01,$f5,$20,$89,$f6,$6c,$3b,$38,$08,$ed,$07,$02,$eb,$f8,$4c,$07,$03,$6c,$38,$ec + .byte $28,$db,$02,$a5,$00,$20,$8e,$0a,$20,$89,$f6,$29,$d7,$03,$e2,$00,$60,$00,$20,$8e,$fd,$20,$ce,$0a,$20,$d5,$0e,$20,$c9,$09,$20,$89 + +; 3D mesh line definition: start points (0, 0, 0, ...) +LineStartPoint: ; b00 + ; spaceship (29 lines) + .byte $00,$00,$00,$00,$01,$02,$03,$04 + .byte $06,$08,$09,$0a,$0b,$0c,$0d,$0e + .byte $0f,$10,$11,$12,$13,$05,$07,$13 + .byte $14,$15,$17,$19,$1a + + ; cube (12 lines) + .byte $1b,$1c,$1d + .byte $1e,$1f,$20,$21,$22,$1b,$1c,$1d + .byte $1e + + ; junk + .byte $26,$27,$28,$29,$2a,$2b,$2d,$2e,$30,$30,$30,$30,$31,$32,$33,$34,$36,$38,$39,$3a,$3b,$3c,$3d + .byte $3e,$3f,$40,$41,$42,$43,$35,$37,$43,$44,$45,$47,$49,$48,$4b,$4c,$4d,$4e,$4f,$50,$4b,$57,$59,$51,$5d,$63,$5d,$5e,$5f,$65,$5f,$60 + .byte $5b,$60,$61,$66,$67,$5c,$5d,$62,$63,$6a,$5e,$5f,$64,$65,$6d,$70,$71,$72,$73,$74,$75,$76,$77,$78,$79,$7a,$7b,$7c,$7d,$7e,$7f,$80 + .byte $81,$7e,$7f,$7e,$7f,$84,$85,$84,$85,$86,$87,$88,$88,$88,$8c,$8c,$8d,$8e,$89,$8a,$91,$92,$93,$94,$93,$94,$8b,$8b,$97,$98,$99,$97 + .byte $98,$99,$9a,$9d,$a1,$a9,$9f,$a3,$ab,$9e,$a2,$aa,$a0,$a4,$ac,$39,$a3,$38,$01,$0a,$a5,$03,$4d,$d6,$03,$4a,$37,$38,$25,$39,$11,$3c + .byte $00,$29,$71,$28,$71,$00,$20,$da,$0b,$4c,$45,$08,$20,$89,$f6,$e8,$11,$3c,$00,$32,$b0,$71,$e0,$71,$22,$00,$6c,$08,$00,$14,$cd,$fe + .byte $00,$20,$ce,$0a,$20,$89,$f6,$29,$3a,$28,$3b,$eb,$00,$20,$8e,$0a,$20,$89,$f6,$29,$38,$2a,$39,$f8,$28,$b9,$f8,$00,$20,$cc,$0b,$20 + +; 3D mesh line definition: end points (1, 2, 3, ...) +LineEndPoint: ; c00 + + ; spaceship (29 lines) + .byte $01,$02,$03,$04,$05,$06,$07,$08 + .byte $09,$0a,$0b,$0c,$0d,$0e,$0f,$10 + .byte $11,$12,$13,$14,$14,$15,$16,$15 + .byte $16,$16,$19,$1a,$18 + + ; cube (12 lines) + .byte $1c,$1d,$1e + .byte $1b,$20,$21,$22,$1f,$1f,$20,$21 + .byte $22 + + ; junk + .byte $27,$28,$29,$2a,$2b,$2c,$2f,$2f,$31,$32,$33,$34,$35,$36,$37,$38,$39,$3a,$3b,$3c,$3d,$3e,$3f + .byte $40,$41,$42,$43,$44,$44,$45,$46,$45,$46,$46,$49,$4a,$4a,$51,$52,$53,$54,$55,$56,$50,$58,$5a,$56,$5e,$64,$63,$64,$60,$66,$65,$66 + .byte $67,$67,$68,$68,$69,$6a,$6a,$6b,$6b,$6c,$6d,$6d,$6e,$6e,$6f,$71,$72,$73,$70,$75,$76,$77,$74,$79,$7a,$7b,$78,$7d,$7e,$7f,$7c,$82 + .byte $83,$81,$80,$85,$84,$80,$81,$60,$5d,$84,$85,$89,$8a,$8b,$8d,$8e,$8f,$90,$91,$92,$93,$94,$95,$96,$97,$98,$9b,$9c,$99,$9a,$9a,$9b + .byte $9c,$9b,$9c,$a5,$a9,$ad,$a7,$ab,$af,$a6,$aa,$ae,$a8,$ac,$b0,$10,$dd,$08,$3f,$19,$6b,$0f,$00,$20,$d0,$09,$20,$0c,$fd,$c9,$d3,$66 + .byte $33,$20,$ce,$0a,$a9,$ff,$85,$2f,$d0,$00,$20,$61,$0c,$20,$89,$f6,$11,$00,$02,$29,$d4,$06,$04,$49,$51,$01,$f8,$4a,$06,$03,$51,$01 + .byte $fa,$21,$3a,$f2,$42,$51,$d3,$07,$fb,$19,$00,$02,$11,$7c,$03,$24,$71,$2a,$71,$00,$20,$7b,$0d,$24,$33,$10,$0c,$20,$0c,$fd,$c9,$83 + +; +; For shape N, the index of the first point. +; +; Shape #0 uses points $00-1A, shape #1 uses points $1B-22. (Data at offsets 2- +; 15 is junk.) +FirstPointIndex: + .byte $00,$1b + .byte $08,$0c,$0d,$1d,$2d,$30,$4b,$5b,$88,$7c,$03,$61,$39,$e9 + +; For shape N, the index of the last point + 1. +LastPointIndex: + .byte $1b,$23 + .byte $0c,$0d,$1d,$2d,$30,$4b,$5b,$88,$b1,$2a,$1a,$00,$02,$ba + +; +; For shape N, the index of the first line. +; +; Shape #0 uses lines $00-1C, shape #1 uses lines $1D-28. (Data at offsets 2-15 +; is junk.) + +FirstLineIndex: + .byte $00,$1d + .byte $0c,$01,$12,$20,$2f,$31,$4e,$58,$8b,$01,$9c,$00,$a5,$16 + +; For shape N, the index of the last point + 1. +LastLineIndex: + .byte $1d,$29 + .byte $12,$01,$20,$2f,$31,$4e,$58,$8b,$af,$fe,$4c,$45,$08,$20 + +; +; Indexes into the rotation tables. One entry for each rotation value (0-27). +; The "low" and "high" tables have the same value at each position, just shifted +; over 4 bits. +; +; Mathematically, cosine has the same shape as sine, but is shifted by PI/2 (one +; quarter period) ahead of it. That's why there are two sets of tables, one of +; which is shifted by 7 bytes. +; +; See the comments above RotTabLo for more details. +; + +RotIndexLo_sin: + .byte $70,$60,$50,$40,$30,$20,$10,$00 + .byte $10,$20,$30,$40,$50,$60,$70,$80 + .byte $90,$a0,$b0,$c0,$d0,$e0,$d0,$c0 + .byte $b0,$a0,$90,$80 + +RotIndexHi_sin: + .byte $07,$06,$05,$04,$03,$02,$01,$00 + .byte $01,$02,$03,$04,$05,$06,$07,$08 + .byte $09,$0a,$0b,$0c,$0d,$0e,$0d,$0c + .byte $0b,$0a,$09,$08 + +RotIndexLo_cos: + .byte $00,$10,$20,$30,$40,$50,$60,$70 + .byte $80,$90,$a0,$b0,$c0,$d0,$e0,$d0 + .byte $c0,$b0,$a0,$90,$80,$70,$60,$50 + .byte $40,$30,$20,$10 + +RotIndexHi_cos: + .byte $00,$01,$02,$03,$04,$05,$06,$07 + .byte $08,$09,$0a,$0b,$0c,$0d,$0e,$0d + .byte $0c,$0b,$0a,$09,$08,$07,$06,$05 + .byte $04,$03,$02,$01 + +; +; Indexes into the scale tables. One entry for each scale value (0-15). See +; the comments above ScaleTabLo for more details. +; +ScaleIndexLo: + .byte $00,$10,$20,$30,$40,$50,$60,$70,$80,$90,$a0,$b0,$c0,$d0,$e0,$f0 + +ScaleIndexHi: + .byte $00,$01,$02,$03,$04,$05,$06,$07,$08,$09,$0a,$0b,$0c,$0d,$0e,$0f + +; +; Junk, pads to end of 256-byte page. +.align $0100 ; (48 bytes) + diff --git a/graphics/hgr/budge3d/basic_example/ship_cube.s b/graphics/hgr/budge3d/basic_example/ship_cube.s new file mode 100644 index 00000000..1e65d97f --- /dev/null +++ b/graphics/hgr/budge3d/basic_example/ship_cube.s @@ -0,0 +1,1170 @@ +;============================================================================ +; Disassembly of a module generated by Bill Budge's 3-D Graphics System and +; Game Tool. +; +; The tool itself is copyright 1980 California Pacific Computer Co. Modules +; may be marketed and sold so long as they provide a credit notice. +; +; The HRCG (code and font) is credited to Christopher Espinosa. +;=========================================================================== +; Disassembly by Andy McFadden, using 6502bench SourceGen v1.6. +; Last updated 2020/03/11 +; +; The manual refers to "points" and "lines" rather than "vertices" and +; "edges". For consistency the same nomenclature is used here. +; +; Two shapes are defined: the space shuttle model from the manual, and a +; simple cube 11 units on a side. The module is configured for XOR drawing, +; Applesoft BASIC interface, and includes the hi-res character generator. +; +; This makes extensive use of self-modifying code. Labels that begin with an +; underscore indicate self-modification targets. +;=========================================================================== +; Code interacts with the module by setting values in arrays and calling known +; entry points. The basic setup for Applesoft BASIC is: +; +; 1 DIM CODE%(15), X%(15), Y%(15), SCALE%(15), XROT%(15), YROT%(15), +; ZROT%(15), SX%(15), SY%(15) +; 2 RESET% = 7932:CLR% = 7951:HIRES% = 7983:CRNCH% = 7737:TXTGEN% = 768 +; +; You can define up to 16 shapes. Their parameters are stored in the various +; arrays: +; +; CODE%(n): 0 (do nothing), 1 (transform & draw), 2 (erase previous, +; transform, draw new), 3 (erase). +; X%(n): X coordinate of center (0-255). +; Y%(n): Y coordinate of center (0-191). +; SCALE%(n): scale factor, 0-15. 15 is full size, 0 is 1/16th. +; XROT%(n): rotation about X axis, 0-27. 0 is no rotation, 27 is just shy +; of 360 degrees. +; YROT%(n): rotation about Y axis. +; ZROT%(n): rotation about Z axis. +; SX%(n): (output) X coordinate of last point drawn. +; SY%(n): (output) Y coordinate of last point drawn. +; +; The code entry points are: +; RESET%: initializes graphics module, clears the screen, switches display +; to primary hi-res page. +; CLR%: clears both hi-res screens and switches to primary hi-res page. +; HIRES%: turns on primary hi-res page. +; CRNCH%: primary animation function. +; +; The "CRUNCH" function: +; - erases objects whose CODE value is 2 or 3 +; - computes new transformations for objects whose CODE value is 1 or 2 +; - draws objects whose CODE value is 1 or 2 +; - flips the display to the other page +; +; When configured for Integer BASIC, some of the array management is simpler, +; and an additional "missile" facility is available. +; +; When configured for use with assembly language, the various arrays live at +; fixed offsets starting around $6000. +;============================================================================ + +.include "zp.inc" + +.include "hardware.inc" + +; org $0300 + +.include "hgr_textgen.s" + +;=========================================================== +; If configured without the HRCG, the module starts here. * +;=========================================================== + +;=========================================================== +; Note that all tables are page-aligned for performance. * +;=========================================================== + +.include "shapes.s" + +; +; These four buffers hold transformed points in screen coordinates. The points +; are in the same order as they are in the mesh definition. +; +; One pair of tables holds the X/Y screen coordinates from the previous frame, +; the other pair of tables holds the coordinates being transformed for the +; current frame. We need two sets because we're display set 0 while generating +; set 1, and after we flip we need to use set 0 again to erase the display. +; +; ---------- +; +; Computed X coordinate, set 0. +XCoord0_0E: ; 0e00 + .byte $00 + .align $100 + +; Computed Y coordinate, set 0. +YCoord0_0F: ; 0f00 + .byte $00 + .align $100 + +; Computed X coordinate, set 1. +XCoord1_10: ; 1000 + .byte $00 + .align $100 + +; Computed Y coordinate, set 1. +YCoord1_11: ; 1100 + .byte $00 + .align $100 + +.include "math_constants.s" + +.include "hgr_tables.s" + +.include "scale_constants.s" + +; +; Draw a list of lines using exclusive-or, which inverts the pixels. Drawing +; the same thing twice erases it. +; +; On entry: +; $45 - index of first line +; $46 - index of last line +; XCoord_0E/YCoord_0F or XCoord_10/YCoord_11 have transformed points in screen +; coordinates +; +; When the module is configured for OR-mode drawing, this code is replaced with +; a dedicated erase function. The erase code is nearly identical to the draw +; code, but saves a little time by simply zeroing out whole bytes instead of +; doing a read-modify-write. +; + +; Clear variables + +DrawLineListEOR: + ldx FIRST_LINE ; 3 start with the first line in this object +DrawLoop: + lda LineStartPoint,X; 4+ get X0,Y0 + tay ; 2 +_0E_or_10_1: + lda XCoord0_0E,Y ; 4+ the instructions here are modified to load from + sta XSTART ; 3 the appropriate set of X/Y coordinate tables +_0F_or_11_1: + lda YCoord0_0F,Y ; 4+ + sta YSTART ; 3 + lda LineEndPoint,X ; 4+ get X1,Y1 + tay ; 2 +_0E_or_10_2: + lda XCoord0_0E,Y ; 4+ + sta XEND ; 3 +_0F_or_11_2: + lda YCoord0_0F,Y ; 4+ + sta YEND ; 3 + stx LINE_INDEX ; 3 save this off + +; Prep the line draw code. We need to compute deltaX/deltaY, and set a register +; increment / decrement / no-op instruction depending on which way the line is +; going. + + lda XSTART ; 3 compute delta X + sec ; 2 + sbc XEND ; 3 + bcs L1A2F ; 2+ left to right + eor #$ff ; 2 right to left; invert value + adc #$01 ; 2 + ldy #OpINX ; 2 + bne GotDeltaX ; 3 + +L1A2F: + beq IsVertical ; 2+ branch if deltaX=0 + ldy #OpDEX ; 2 + bne GotDeltaX ; 3 + +IsVertical: + ldy #OpNOP ; 2 fully vertical, use no-op +GotDeltaX: + sta DELTA_X ; 3 + sty _InxDexNop1 ; 4 + sty _InxDexNop2 ; 4 + lda YSTART ; 3 compute delta Y + sec ; 2 + sbc YEND ; 3 + bcs L1A4E ; 2+ end < start, we're good + eor #$ff ; 2 invert value + adc #$01 ; 2 + ldy #OpINY ; 2 + bne GotDeltaY ; 3 + +L1A4E: + beq IsHorizontal ; 2+ branch if deltaY=0 + ldy #OpDEY ; 2 + bne GotDeltaY ; 3 + +IsHorizontal: + ldy #OpNOP ; 2 fully horizontal, use no-op +GotDeltaY: + sta DELTA_Y ; 3 + sty _InyDeyNop1 ; 4 + sty _InyDeyNop2 ; 4 + ldx XSTART ; 3 + ldy YSTART ; 3 + lda #$00 ; 2 + sta LINE_ADJ ; 3 + lda DELTA_X ; 3 + cmp DELTA_Y ; 3 + bcs HorizDomLine ; 2+ + +; Line draw: vertically dominant (move vertically every step) +; +; On entry: X=xpos, Y=ypos + +VertDomLine: + cpy YEND ; 3 + beq LineDone ; 2+ +_InyDeyNop1: + nop ; 2 self-mod INY/DEY/NOP + lda YTableLo,Y ; 4+ new line, update Y position + sta HPTR ; 3 + lda YTableHi,Y ; 4+ + ora HPAGE ; 3 + sta HPTR+1 ; 3 + lda LINE_ADJ ; 3 Bresenham update + clc ; 2 + adc DELTA_X ; 3 + cmp DELTA_Y ; 3 + bcs NewColumn ; 2+ + sta LINE_ADJ ; 3 + bcc SameColumn ; 3 + +NewColumn: + sbc DELTA_Y ; 3 + sta LINE_ADJ ; 3 +_InxDexNop1: + nop ;2 self-mod INX/DEX/NOP +SameColumn: + sty YSAVE ; 3 + ldy Div7Tab,X ; 4+ XOR-draw the point + lda (HPTR),Y ; 5+ + eor HiResBitTab,X ; 4+ + sta (HPTR),Y ; 6 + ldy YSAVE ; 3 + jmp VertDomLine ; 3 + +LineDone: + ldx LINE_INDEX ; 3 + inx ; 2 + cpx LAST_LINE ; 3 reached end? + beq DrawDone ; 2+ + jmp DrawLoop ; 3 + +DrawDone: + rts ; 6 + +; Line draw: horizontally dominant (move horizontally every step) +; +; On entry: X=xpos, Y=ypos + +HorizDomLine: + lda YTableLo,Y ; 4+ set up hi-res pointer + sta HPTR ; 3 + lda YTableHi,Y ; 4+ + ora HPAGE ; 3 + sta HPTR+1 ; 3 +HorzLoop: + cpx XEND ; 3 X at end? + beq LineDone ; 2+ yes, finish +_InxDexNop2: + nop ; 2 + lda LINE_ADJ ; 3 Bresenham update + clc ; 2 + adc DELTA_Y ; 3 + cmp DELTA_X ; 3 + bcs NewRow ; 2+ + sta LINE_ADJ ; 3 + bcc SameRow ; 3 + +NewRow: + sbc DELTA_X ; 3 + sta LINE_ADJ ; 3 +_InyDeyNop2: + nop ; 2 + lda YTableLo,Y ; 4+ update Y position + sta HPTR ; 3 + lda YTableHi,Y ; 4+ + ora HPAGE ; 3 + sta HPTR+1 ; 3 +SameRow: + sty YSAVE ; 3 + ldy Div7Tab,X ; 4+ XOR-draw the point + lda (HPTR),Y ; 5+ + eor HiResBitTab,X ; 4+ + sta (HPTR),Y ; 6 + ldy YSAVE ; 3 + jmp HorzLoop ; 3 + + ; Draw code calls here. Since we're configured for XOR mode, this just jumps to + ; the exclusive-or version. If we were configured for OR mode, this would be + ; LDX $45 / LDA $0B00,X instead of a JMP. +DrawLineList: + jmp DrawLineListEOR ; 3 + + ; + ; Unused OR-mode implementation follows. + ; + ; The code is substantially similar to the exclusive-or version, so it has not + ; been annotated. + ; +.byte $00,$0b ; ? + + tay ; 2 + lda XCoord0_0E,Y ; 4+ + sta XSTART ; 3 + lda YCoord0_0F,Y ; 4+ + sta YSTART ; 3 + lda LineEndPoint,X ; 4+ + tay ; 2 + lda XCoord0_0E,Y ; 4+ + sta XEND ; 3 + lda YCoord0_0F,Y ; 4+ + sta YEND ; 3 + stx LINE_INDEX ; 3 + lda XSTART ; 3 + sec ; 2 + sbc XEND ; 3 + bcs L1B1A ; 2+ + eor #$ff ; 2 + adc #$01 ; 2 + ldy #$e8 ; 2 + bne L1B22 ; 3 + +L1B1A: + beq L1B20 ; 2+ + ldy #$ca ; 2 + bne L1B22 ; 3 + +L1B20: + ldy #$ea ; 2 +L1B22: + sta DELTA_X ; 3 + sty L1B79 ; 4 + sty L1BA6 ; 4 + lda YSTART ; 3 + sec ; 2 + sbc YEND ; 3 + bcs L1B39 ; 2+ + eor #$ff ; 2 + adc #$01 ; 2 + ldy #$c8 ; 2 + bne L1B41 ; 3 + +L1B39: + beq L1B3F ; 2+ + ldy #$88 ; 2 + bne L1B41 ; 3 + +L1B3F: + ldy #$ea ; 2 +L1B41: + sta DELTA_Y ; 3 + sty L1B5B ; 4 + sty L1BB8 ; 4 + ldx XSTART ; 3 + ldy YSTART ; 3 + lda #$00 ; 2 + sta LINE_ADJ ; 3 + lda DELTA_X ; 3 + cmp DELTA_Y ; 3 + bcs L1B96 ; 2+ +L1B57: + cpy YEND ; 3 + beq L1B8B ; 2+ +L1B5B: + nop ; 2 + lda YTableLo,Y ; 4+ + sta HPTR ; 3 + lda YTableHi,Y ; 4+ + ora HPAGE ; 3 + sta HPTR+1 ; 3 + lda LINE_ADJ ; 3 + clc ; 2 + adc DELTA_X ; 3 + cmp DELTA_Y ; 3 + bcs L1B75 ; 2+ + sta LINE_ADJ ; 3 + bcc L1B7A ; 3 + +L1B75: + sbc DELTA_Y ; 3 + sta LINE_ADJ ; 3 +L1B79: + nop ; 2 +L1B7A: + sty YSAVE ; 3 + ldy Div7Tab,X ; 4+ + lda (HPTR),Y ; 5+ + ora HiResBitTab,X ; 4+ + sta (HPTR),Y ; 6 + ldy YSAVE ; 3 + jmp L1B57 ; 3 + +L1B8B: + ldx LINE_INDEX ; 3 + inx ; 2 + cpx LAST_LINE ; 3 + beq L1B95 ; 2+ + jmp DrawLineList+2 ; 3 + +L1B95: + rts ; 6 + +L1B96: + lda YTableLo,Y ; 4+ + sta HPTR ; 3 + lda YTableHi,Y ; 4+ + ora HPAGE ; 3 + sta HPTR+1 ; 3 +L1BA2: + cpx XEND ; 3 + beq L1B8B ; 2+ +L1BA6: + nop ; 2 + lda LINE_ADJ ; 3 + clc ; 2 + adc DELTA_Y ; 3 + cmp DELTA_X ; 3 + bcs L1BB4 ; 2+ + sta LINE_ADJ ; 3 + bcc L1BC5 ; 3 + +L1BB4: + sbc DELTA_X ; 3 + sta LINE_ADJ ; 3 +L1BB8: + nop ; 2 + lda YTableLo,Y ; 4+ + sta HPTR ; 3 + lda YTableHi,Y ; 4+ + ora HPAGE ; 3 + sta HPTR+1 ; 3 +L1BC5: + sty YSAVE ; 3 + ldy Div7Tab,X ; 4+ + lda (HPTR),Y ; 5+ + ora HiResBitTab,X ; 4+ + sta (HPTR),Y ; 6 + ldy YSAVE ; 3 + jmp L1BA2 ; 3 + +; +; Unreferenced function that copies untransformed mesh X/Y values into the +; screen-coordinate buffers. Could be used for a static 2D effect, like a +; background that doesn't move. +; + ldx FIRST_LINE ; 3 +UnusedCopyLoop: + lda ShapeXCoords,X ; 4+ + clc ; 2 + adc YSTART ; 3 +_unused_mod0: + sta XCoord0_0E,X ; 5 + lda XEND ; 3 + sec ; 2 + sbc ShapeYCoords,X ; 4+ +_unused_mod1: + sta YCoord0_0F,X ; 5 + inx ; 2 + cpx LAST_LINE ; 3 + bne UnusedCopyLoop ; 2+ + rts ; 6 + + ; Current hi-res page. + ; + ; $00 = draw page 1, show page 2 + ; $FF = draw page 2, show page 1 +CurPage: .byte $ff + + ; + ; Switch to the other hi-res page. + ; +SwapPage: + lda CurPage ; 4 + eor #$ff ; 2 flip to other page + sta CurPage ; 4 + beq DrawOnPage1 ; 3+ + sta TXTPAGE1 ; 4 draw on page 2, show page 1 + lda #$40 ; 2 + ldx #>XCoord1_10 ; 2 + ldy #>YCoord1_11 ; 2 + bne L1C0F ; 3 + +DrawOnPage1: + sta TXTPAGE2 ; 4 draw on page 1, show page 2 + lda #$20 ; 2 + ldx #>XCoord0_0E ; 2 + ldy #>YCoord0_0F ; 2 + + ; Save the hi-res page, and modify the instructions that read from or write data + ; to the transformed point arrays. +L1C0F: + sta HPAGE ; 3 + stx _0E_or_10_1+2 ; 4 + stx _0E_or_10_2+2 ; 4 + stx _0E_or_10_3+2 ; 4 + stx _unused_mod0+2 ; 4 + sty _0F_or_11_1+2 ; 4 + sty _0F_or_11_2+2 ; 4 + sty _0F_or_11_3+2 ; 4 + sty _unused_mod1+2 ; 4 + rts ; 6 + +; Unreferenced junk (12 bytes) +.byte $00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00 + +; Coordinate transformation function. Transforms all points in a single object. +; +; On entry: +; 1c = scale (00-0f) +; 1d = xc (00-ff) +; 1e = yc (00-bf) +; 1f = zrot (00-1b) +; 3c = yrot (00-1b) +; 3d = xrot (00-1b) +; 45 = index of first point to transform +; 46 = index of last point to transform +; +; Rotation values greater than $1B, and scale factors greater than $0F, disable +; the calculation. This has the same effect as a rotation value of 0 or a scale +; of 15, but is more efficient, because this uses self-modifying code to skip +; the computation entirely. +; + +; Clear variables +xc = $19 ; transformed X coordinate +yc = $1a ; transformed Y coordinate +zc = $1b ; transformed Z coordinate +scale = $1c ; $00-0F, where $0F is full size +xposn = $1d ; X coordinate (0-255) +yposn = $1e ; Y coordinate (0-191) +zrot = $1f ; Z rotation ($00-1B) +yrot = $3c ; Y rotation ($00-1B) +xrot = $3d ; X rotation ($00-1B) +rot_tmp = $3f +out_index = $43 +first_point = $45 +last_point = $46 + +CompTransform: + ldx first_point ; 3 get first point index; this stays in X for a while + + ; Configure Z rotation. + + ldy zrot ; 3 + cpy #$1c ; 2 valid rotation value? + bcc ConfigZrot ; 2+ yes, configure + lda #DoTranslate ; 2 + sta _BeforeScale+2 ; 4 + bne TransformLoop ; 4 + +SetScale: + lda #DoScale ; 2 + sta _BeforeScale+2 ; 4 + lda ScaleIndexLo,Y ; 4+ $00, $10, $20, ... $F0 + sta _scaleLX+1 ; 4 + sta _scaleLY+1 ; 4 + lda ScaleIndexHi,Y ; 4+ $00, $01, $02, ... $0F + sta _scaleHX+1 ; 4 + sta _scaleHY+1 ; 4 + + + ; + ; Now that we've got the code modified, perform the computation for all points + ; in the object. + ; +TransformLoop: + lda ShapeXCoords,X ; 4+ + sta xc ; 3 + lda ShapeYCoords,X ; 4+ + sta yc ; 3 + lda ShapeZCoords,X ; 4+ + sta zc ; 3 + stx out_index ; 3 save for later +_BeforeZrot: + jmp DoZrot ; 3 + +DoZrot: + lda xc ; 3 rotating about Z, so we need to update X/Y coords + and #$0f ; 2 split X/Y into nibbles + sta rot_tmp ; 3 + lda xc ; 3 + and #$f0 ; 2 + sta rot_tmp+1 ; 3 + lda yc ; 3 + and #$0f ; 2 + sta rot_tmp+2 ; 3 + lda yc ; 3 + and #$f0 ; 2 + sta rot_tmp+3 ; 3 + ldy rot_tmp ; 3 transform X coord + ldx rot_tmp+1 ; 3 XC = X * cos(theta) - Y * sin(theta) +_zrotLC1: + lda RotTabLo,Y ; 4+ + clc ; 2 +_zrotHC1: + adc RotTabHi,X ; 4+ + ldy rot_tmp+2 ; 3 + ldx rot_tmp+3 ; 3 + sec ; 2 +_zrotLS1: + sbc RotTabLo,Y ; 4+ + sec ; 2 +_zrotHS1: + sbc RotTabHi,X ; 4+ + sta xc ; 3 save updated coord +_zrotLC2: + lda RotTabLo,Y ; 4+ transform Y coord + clc ; 2 YC = Y * cos(theta) + X * sin(theta) +_zrotHC2: + adc RotTabHi,X ; 4+ + ldy rot_tmp ; 3 + ldx rot_tmp+1 ; 3 + clc ; 2 +_zrotLS2: + adc RotTabLo,Y ; 4+ + clc ; 2 +_zrotHS2: + adc RotTabHi,X ; 4+ + sta yc ; 3 save updated coord +_BeforeYrot: + jmp DoYrot ; 3 + +DoYrot: + lda xc ; 3 rotating about Y, so update X/Z + and #$0f ; 2 + sta rot_tmp ; 3 + lda xc ; 3 + and #$f0 ; 2 + sta rot_tmp+1 ; 3 + lda zc ; 3 + and #$0f ; 2 + sta rot_tmp+2 ; 3 + lda zc ; 3 + and #$f0 ; 2 + sta rot_tmp+3 ; 3 + ldy rot_tmp ; 3 + ldx rot_tmp+1 ; 3 +_yrotLC1: + lda RotTabLo,Y ; 4+ + clc ; 2 +_yrotHC1: + adc RotTabHi,X ; 4+ + ldy rot_tmp+2 ; 3 + ldx rot_tmp+3 ; 3 + sec ; 2 +_yrotLS1: + sbc RotTabLo,Y ; 4+ + sec ; 2 +_yrotHS1: + sbc RotTabHi,X ; 4+ + sta xc ; 3 +_yrotLC2: + lda RotTabLo,Y ; 4+ + clc ; 2 +_yrotHC2: + adc RotTabHi,X ; 4+ + ldy rot_tmp ; 3 + ldx rot_tmp+1 ; 3 + clc ; 2 +_yrotLS2: + adc RotTabLo,Y ; 4+ + clc ; 2 +_yrotHS2: + adc RotTabHi,X ; 4+ + sta zc ; 3 +_BeforeXrot: + jmp DoXrot ; 3 + +DoXrot: + lda zc ; 3 rotating about X, so update Z/Y + and #$0f ; 2 + sta rot_tmp ; 3 + lda zc ; 3 + and #$f0 ; 2 + sta rot_tmp+1 ; 3 + lda yc ; 3 + and #$0f ; 2 + sta rot_tmp+2 ; 3 + lda yc ; 3 + and #$f0 ; 2 + sta rot_tmp+3 ; 3 + ldy rot_tmp ; 3 + ldx rot_tmp+1 ; 3 +_xrotLC1: + lda RotTabLo,Y ; 4+ + clc ; 2 +_xrotHC1: + adc RotTabHi,X ; 4+ + ldy rot_tmp+2 ; 3 + ldx rot_tmp+3 ; 3 + sec ; 2 +_xrotLS1: + sbc RotTabLo,Y ; 4+ + sec ; 2 +_xrotHS1: + sbc RotTabHi,X ; 4+ + sta zc ; 3 +_xrotLC2: + lda RotTabLo,Y ; 4+ + clc ; 2 +_xrotHC2: + adc RotTabHi,X ; 4+ + ldy rot_tmp ; 3 + ldx rot_tmp+1 ; 3 + clc ; 2 +_xrotLS2: + adc RotTabLo,Y ; 4+ + clc ; 2 +_xrotHS2: + adc RotTabHi,X ; 4+ + sta yc ; 3 +_BeforeScale: + jmp DoScale ; 3 + + + ; Apply scaling. Traditionally this is applied before rotation. +DoScale: + lda xc ; 3 scale the X coordinate + and #$f0 ; 2 + tax ; 2 + lda xc ; 3 + and #$0f ; 2 + tay ; 2 +_scaleLX: + lda ScaleTabLo,Y ; 4+ + clc ; 2 +_scaleHX: + adc ScaleTabHi,X ; 4+ + sta xc ; 3 + lda yc ; 3 scale the Y coordinate + and #$f0 ; 2 + tax ; 2 + lda yc ; 3 + and #$0f ; 2 + tay ; 2 +_scaleLY: + lda ScaleTabLo,Y ; 4+ + clc ; 2 +_scaleHY: + adc ScaleTabHi,X ; 4+ + sta yc ; 3 + + ; + ; Apply translation. + ; + ; This is the final step, so the result is written to the transformed-point + ; arrays. + ; +DoTranslate: + ldx out_index ; 3 + lda xc ; 3 + clc ; 2 + adc xposn ; 3 object center in screen coordinates +_0E_or_10_3: + sta XCoord0_0E,X ; 5 + lda yposn ; 3 + sec ; 2 + sbc yc ; 3 +_0F_or_11_3: + sta YCoord0_0F,X ; 5 + inx ; 2 + cpx last_point ; 3 done? + beq TransformDone ; 2+ yes, bail + jmp TransformLoop ; 3 + +TransformDone: + rts ; 6 + +SavedShapeIndex: + .byte $ad ;holds shape index while we work + +;******************************************************************************* +; CRUNCH/CRNCH% entry point * +; * +; For each object, do what CODE%(n) tells us to: * +; * +; 0 - do nothing * +; 1 - transform and draw * +; 2 - erase, transform, draw * +; 3 - erase * +;******************************************************************************* + +;FIRST_LINE = $45 +;LAST_LINE = $46 + +CRUNCH: + jsr Setup ; 6 find Applesoft arrays + + ;============================== + ; First pass: erase old shapes + ;============================== + + lda NumObjects ; 4 number of defined objects + asl ; 2 * 2 + tax ; 2 use as index +ShapeLoop: + dex ; 2 + dex ; 2 + bmi Transform ; 2+ done +_codeAR1: + lda CODE_arr,X ; 4+ + cmp #$02 ; 2 2 or 3? + bcc ShapeLoop ; 2+ no, move on + stx SavedShapeIndex ; 4 + txa ; 2 + lsr ; 2 + tax ; 2 + lda FirstLineIndex,X; 4+ + sta FIRST_LINE ; 3 + lda LastLineIndex,X ; 4+ + sta LAST_LINE ; 3 + cmp FIRST_LINE ; 3 is number of lines <= 0? + bcc NoLines1 ; 2+ + beq NoLines1 ; 2+ yes, skip draw + jsr DrawLineListEOR ; 6 erase with EOR version, regardless of config +NoLines1: + ldx SavedShapeIndex ; 4 + bpl ShapeLoop ; 3 ...always + + ;=============================== + ; Second pass: transform shapes + ;=============================== +Transform: + lda NumObjects ; 4 + asl ; 2 + tax ; 2 +TransLoop: + dex ; 2 + dex ; 2 + bmi DrawNew ; 2+ +_codeAR2: + lda CODE_arr,X ; 4+ + beq TransLoop ; 2+ is it zero or three? + cmp #$03 ; 2 + beq TransLoop ; 2+ yes, we only draw on 1 or 2 + + ; Extract the scale, X/Y, and rotation values out of the arrays and copy them to + ; zero-page locations. + +_scaleAR: + lda SCALE_arr,X ; 4+ + sta scale ; 3 +_xAR: + lda X_arr,X ; 4+ + sta xposn ; 3 +_yAR: + lda Y_arr,X ; 4+ + sta yposn ; 3 +_zrotAR: + lda ZROT_arr,X ; 4+ + sta zrot ; 3 +_yrotAR: + lda YROT_arr,X ; 4+ + sta yrot ; 3 +_xrotAR: + lda XROT_arr,X ; 4+ + sta xrot ; 3 + stx SavedShapeIndex ; 4 save this off + txa ; 2 + lsr ; 2 convert to 1x index + tax ; 2 + lda FirstPointIndex,X; 4+ + sta FIRST_LINE ; 3 (actually first_point) + lda LastPointIndex,X; 4+ + sta LAST_LINE ; 3 + cmp FIRST_LINE ; 3 is number of points <= 0? + bcc NoPoints ; 2+ + beq NoPoints ; 2+ yes, skip transform + jsr CompTransform ; 6 transform all points +NoPoints: + ldx SavedShapeIndex ; 4 + lda xc ; 3 + clc ; 2 + adc xposn ; 3 +_sxAR: + sta SX_arr,X ; 5 + lda yposn ; 3 + sec ; 2 + sbc yc ; 3 +_syAR: + sta SY_arr,X ; 5 + jmp TransLoop ; 3 + + ;============================= + ; Third pass: draw shapes + ;============================= + +DrawNew: + lda NumObjects ; 4 + asl ; 2 + tax ; 2 +L1ECE: + dex ; 2 + dex ; 2 + bmi L1EF9 ; 2+ +_codeAR3: + lda CODE_arr,X ; 4+ is it 0 or 3? + beq L1ECE ; 2+ + cmp #$03 ; 2 + beq L1ECE ; 2+ yup, no draw + stx SavedShapeIndex ; 4 save index + txa ; 2 + lsr ; 2 convert it back to 1x index + tax ; 2 + lda FirstLineIndex,X; 4+ draw all the lines in the shape + sta FIRST_LINE ; 3 + lda LastLineIndex,X ; 4+ + sta LAST_LINE ; 3 + cmp FIRST_LINE ; 3 is number of lines <= 0? + bcc NoLines2 ; 2+ + beq NoLines2 ; 2+ yes, skip draw + jsr DrawLineList ; 6 draw all lines +NoLines2: + ldx SavedShapeIndex ; 4 + bpl L1ECE ; 3 ...always + +L1EF9: + jmp SwapPage ; 3 + + +;******************************************************************************* +; RESET entry point * +; * +; Zeroes out the CODE% array, erases both hi-res screens, and enables display * +; of the primary hi-res page. * +;******************************************************************************* + +RESET: + jsr Setup ; 6 sets A=0, Y=$1E +_codeAR4: + sta CODE_arr,y ; 5 zero out CODE% + dey ; 2 + bpl _codeAR4 ; 3+ + jsr CLEAR ; 6 + jsr SwapPage ; 6 + jsr SwapPage ; 6 + rts ; 6 + +;******************************************************************************* +; CLEAR/CLR% entry point * +; * +; Clears both hi-res pages. * +;******************************************************************************* +; Clear variables + +ptr1 = $1a +ptr2 = $1c + +CLEAR: + lda #$20 ; 2 hi-res page 1 + sta ptr1+1 ; 3 + lda #$40 ; 2 hi-res page 2 + sta ptr2+1 ; 3 + ldy #$00 ; 2 + sty ptr1 ; 3 + sty ptr2 ; 3 +L1F1D: + tya ; 2 +L1F1E: + sta (ptr1),Y ; 6 erase both pages + sta (ptr2),Y ; 6 + iny ; 2 + bne L1F1E ; 2+ + inc ptr1+1 ; 5 + inc ptr2+1 ; 5 + lda ptr1+1 ; 3 (could hold counter in X-reg) + and #$3f ; 2 + bne L1F1D ; 2+ + +;******************************************************************************* +; HIRES entry point * +; * +; Displays primary hi-res page. * +;******************************************************************************* +HI_RES: + sta HIRES ; 4 + sta MIXCLR ; 4 + sta TXTCLR ; 4 + rts ; 6 + +; Locate Applesoft arrays. This assumes the arrays are declared first, and in +; the correct order, so that the start can be found at a fixed offset from the +; BASIC array table pointer. +; +; 1 DIM CODE%(15), X%(15), Y%(15), SCALE%(15), XROT%(15), YROT%(15), ZROT%(15), +; SX%(15), SY%(15) +; +; If you generate the module for Integer BASIC, this is the entry point for +; MISSILE (7993). + +Setup: + lda BAS_ARYTAB ; 3 CODE% is at +$0008 + clc ; 2 + adc #$08 ; 2 + tax ; 2 + lda BAS_ARYTAB+1 ; 3 + adc #$00 ; 2 + stx _codeAR1+1 ; 4 + sta _codeAR1+2 ; 4 + stx _codeAR2+1 ; 4 + sta _codeAR2+2 ; 4 + stx _codeAR3+1 ; 4 + sta _codeAR3+2 ; 4 + stx _codeAR4+1 ; 4 + sta _codeAR4+2 ; 4 + lda BAS_ARYTAB ; 3 X% is at +$002F + clc ; 2 + adc #$2f ; 2 + tax ; 2 + lda BAS_ARYTAB+1 ; 3 + adc #$00 ; 2 + stx _xAR+1 ; 4 + sta _xAR+2 ; 4 + lda BAS_ARYTAB ; 3 Y% is at +$0056 + clc ; 2 + adc #$56 ; 2 + tax ; 2 + lda BAS_ARYTAB+1 ; 3 + adc #$00 ; 2 + stx _yAR+1 ; 4 + sta _yAR+2 ; 4 + lda BAS_ARYTAB ; 3 SCALE% is at + $007D + clc ; 2 + adc #$7d ; 2 + tax ; 2 + lda BAS_ARYTAB+1 ; 3 + adc #$00 ; 2 + stx _scaleAR+1 ; 4 + sta _scaleAR+2 ; 4 + lda BAS_ARYTAB ; 3 XROT% is at +$00A4 + clc ; 2 + adc #$a4 ; 2 + tax ; 2 + lda BAS_ARYTAB+1 ; 3 + adc #$00 ; 2 + stx _xrotAR+1 ; 4 + sta _xrotAR+2 ; 4 + lda BAS_ARYTAB ; 3 YROT% is at +$00CB + clc ; 2 + adc #$cb ; 2 + tax ; 2 + lda BAS_ARYTAB+1 ; 3 + adc #$00 ; 2 + stx _yrotAR+1 ; 4 + sta _yrotAR+2 ; 4 + lda BAS_ARYTAB ; 3 ZROT% is at +$00F2 + clc ; 2 + adc #$f2 ; 2 + tax ; 2 + lda BAS_ARYTAB+1 ; 3 + adc #$00 ; 2 + stx _zrotAR+1 ; 4 + sta _zrotAR+2 ; 4 + lda BAS_ARYTAB ; 3 SX% is at +$0119 + clc ; 2 + adc #$19 ; 2 + tax ; 2 + lda BAS_ARYTAB+1 ; 3 + adc #$01 ; 2 + stx _sxAR+1 ; 4 + sta _sxAR+2 ; 4 + lda BAS_ARYTAB ; 3 SY% is at +$0140 + clc ; 2 + adc #$40 ; 2 + tax ; 2 + lda BAS_ARYTAB+1 ; 3 + adc #$01 ; 2 + stx _syAR+1 ; 4 + sta _syAR+2 ; 4 + ldy #$1e ; 2 Set A and Y for RESET + lda #$00 ; 2 + rts ; 6 + + ; Junk; pads binary to end of page. +.align $0100 + diff --git a/graphics/hgr/budge3d/test.bas b/graphics/hgr/budge3d/basic_example/test.bas similarity index 100% rename from graphics/hgr/budge3d/test.bas rename to graphics/hgr/budge3d/basic_example/test.bas diff --git a/graphics/hgr/budge3d/basic_example/zp.inc b/graphics/hgr/budge3d/basic_example/zp.inc new file mode 100644 index 00000000..83dfdf87 --- /dev/null +++ b/graphics/hgr/budge3d/basic_example/zp.inc @@ -0,0 +1,33 @@ +OpDEY = $88 ; DEY opcode +OpINY = $c8 ; INY opcode +OpDEX = $ca ; DEX opcode +OpINX = $e8 ; INX opcode +OpNOP = $ea ; NOP opcode + +; zero page addresses + +MON_WNDLEFT = $20 ; left column of scroll window +MON_WNDWDTH = $21 ; width of scroll window +MON_WNDTOP = $22 ; top of scroll window +MON_WNDBTM = $23 ; bottom of scroll window +MON_CH = $24 ; cursor horizontal displacement +MON_CV = $25 ; cursor vertical displacement +MON_CSWL = $36 ; character output hook (lo) +MON_CSWH = $37 ; character output hook (hi) +BAS_ARYTAB = $6b ; pointer to start of Applesoft array space (2b) + +; Clear variables + +HPTR = $06 +HPAGE = $18 ; hi-res page ($20 or $40) +XSTART = $1c +YSTART = $1d +XEND = $1e +YEND = $1f +DELTA_X = $3c +DELTA_Y = $3d +LINE_ADJ = $3e +LINE_INDEX = $43 +YSAVE = $00 +FIRST_LINE = $45 +LAST_LINE = $46 diff --git a/graphics/hgr/budge3d/hardware.inc b/graphics/hgr/budge3d/hardware.inc index 269c4a3c..f64e4e33 100644 --- a/graphics/hgr/budge3d/hardware.inc +++ b/graphics/hgr/budge3d/hardware.inc @@ -1,13 +1,3 @@ -CODE_arr = $6008 ; CODE array (dummy address) -X_arr = $602f ; X array (dummy address) -Y_arr = $6056 ; Y array (dummy address) -SCALE_arr = $607d ; SCALE array (dummy address) -XROT_arr = $60a4 ; XROT array (dummy address) -YROT_arr = $60cb ; YROT array (dummy address) -ZROT_arr = $60f2 ; ZROT array (dummy address) -SX_arr = $6119 ; SX array (dummy address) -SY_arr = $6140 ; SY array (dummy address) - ; soft switches TXTCLR = $c050 ; RW display graphics MIXCLR = $c052 ; RW display full screen diff --git a/graphics/hgr/budge3d/hello.bas b/graphics/hgr/budge3d/hello.bas index 8ef36c79..fba2ddfb 100644 --- a/graphics/hgr/budge3d/hello.bas +++ b/graphics/hgr/budge3d/hello.bas @@ -1,3 +1,2 @@ -10 POKE 24576,0: POKE 103,1: POKE 104,96: POKE 175,1: POKE 176,96 -30 PRINT CHR$ (4)"RUN TEST" +10 PRINT CHR$ (4)"BRUN SHIP_CUBE" diff --git a/graphics/hgr/budge3d/ship_cube.s b/graphics/hgr/budge3d/ship_cube.s index 1e65d97f..fcca6fba 100644 --- a/graphics/hgr/budge3d/ship_cube.s +++ b/graphics/hgr/budge3d/ship_cube.s @@ -14,52 +14,47 @@ ; "edges". For consistency the same nomenclature is used here. ; ; Two shapes are defined: the space shuttle model from the manual, and a -; simple cube 11 units on a side. The module is configured for XOR drawing, -; Applesoft BASIC interface, and includes the hi-res character generator. -; +; simple cube 11 units on a side. The module is configured for XOR drawing + ; This makes extensive use of self-modifying code. Labels that begin with an ; underscore indicate self-modification targets. + +; Note from Vince Weaver +; + I've taken the disassembly and am converting it to assembly language +; I guess it might make more sense to get an assembly language kernel +; from the program, but I'm going to modify the BASIC anyway + + + ;=========================================================================== -; Code interacts with the module by setting values in arrays and calling known -; entry points. The basic setup for Applesoft BASIC is: -; -; 1 DIM CODE%(15), X%(15), Y%(15), SCALE%(15), XROT%(15), YROT%(15), -; ZROT%(15), SX%(15), SY%(15) -; 2 RESET% = 7932:CLR% = 7951:HIRES% = 7983:CRNCH% = 7737:TXTGEN% = 768 ; ; You can define up to 16 shapes. Their parameters are stored in the various ; arrays: ; -; CODE%(n): 0 (do nothing), 1 (transform & draw), 2 (erase previous, +; CODE_arr+N: 0 (do nothing), 1 (transform & draw), 2 (erase previous, ; transform, draw new), 3 (erase). -; X%(n): X coordinate of center (0-255). -; Y%(n): Y coordinate of center (0-191). -; SCALE%(n): scale factor, 0-15. 15 is full size, 0 is 1/16th. -; XROT%(n): rotation about X axis, 0-27. 0 is no rotation, 27 is just shy -; of 360 degrees. -; YROT%(n): rotation about Y axis. -; ZROT%(n): rotation about Z axis. -; SX%(n): (output) X coordinate of last point drawn. -; SY%(n): (output) Y coordinate of last point drawn. +; X_arr+N: X coordinate of center (0-255). +; Y_arr+N: Y coordinate of center (0-191). +; SCALE_arr+N: scale factor, 0-15. 15 is full size, 0 is 1/16th. +; XROT_arr+N: rotation about X axis, 0-27. 0 is no rotation, 27 is just shy +; of 360 degrees. +; YROT_arr+N: rotation about Y axis. +; ZROT_arr+N: rotation about Z axis. +; SX_arr+N: (output) X coordinate of last point drawn. +; SY_arr+N: (output) Y coordinate of last point drawn. ; ; The code entry points are: -; RESET%: initializes graphics module, clears the screen, switches display +; RESET: initializes graphics module, clears the screen, switches display ; to primary hi-res page. -; CLR%: clears both hi-res screens and switches to primary hi-res page. -; HIRES%: turns on primary hi-res page. -; CRNCH%: primary animation function. +; CLR: clears both hi-res screens and switches to primary hi-res page. +; HIRES: turns on primary hi-res page. +; CRUNCH: primary animation function. ; ; The "CRUNCH" function: ; - erases objects whose CODE value is 2 or 3 ; - computes new transformations for objects whose CODE value is 1 or 2 ; - draws objects whose CODE value is 1 or 2 ; - flips the display to the other page -; -; When configured for Integer BASIC, some of the array management is simpler, -; and an additional "missile" facility is available. -; -; When configured for use with assembly language, the various arrays live at -; fixed offsets starting around $6000. ;============================================================================ .include "zp.inc" @@ -68,7 +63,84 @@ ; org $0300 -.include "hgr_textgen.s" +entry: + jsr RESET + + ; CODE[0]=1 -> transform and draw + ; CODE[1]=1 -> transform and draw + + lda #1 + sta CODE_arr + sta CODE_arr+1 + + ; X[0]=127 Y[0]=96:X[1]=20:Y[1]=30 -> co-ord of center + + lda #127 + sta X_arr + lda #96 + sta Y_arr + + lda #20 + sta X_arr+1 + lda #30 + sta Y_arr+1 + + ; SCALE[0]=15:XROT[0]=2:YROT[0]=5:ZROT[0]=0 + ; SCALE[1]=15:XROT[1]=2:YROT[1]=5:ZROT[1]=0 + + lda #15 + sta SCALE_arr + sta SCALE_arr+1 + + lda #2 + sta XROT_arr + sta XROT_arr+1 + + lda #5 + sta YROT_arr + sta YROT_arr+1 + + lda #0 + sta ZROT_arr + sta ZROT_arr+1 + + jsr CRUNCH + jsr CRUNCH + + ; CODE[0]=2:CODE[1]=2 -> erase and draw new + + lda #2 + sta CODE_arr + sta CODE_arr+1 + +loop: + ; ZROT[0]+=1: IF ZEROT[0]==28 THEN ZROT[0]=0 + ; YROT[1]=ZROT[0] + + inc ZROT_arr + lda ZROT_arr + cmp #28 + bne zrot_ok + lda #0 + sta ZROT_arr +zrot_ok: + sta YROT_arr+1 + + jsr CRUNCH + + jmp loop + + nop +.align $100 ; 400 + nop +.align $100 ; 500 + nop +.align $100 ; 600 + nop +.align $100 ; 700 + nop +.align $100 ; 700 + ;=========================================================== ; If configured without the HRCG, the module starts here. * @@ -878,17 +950,17 @@ SavedShapeIndex: ;LAST_LINE = $46 CRUNCH: - jsr Setup ; 6 find Applesoft arrays +; jsr Setup ; 6 find Applesoft arrays ;============================== ; First pass: erase old shapes ;============================== lda NumObjects ; 4 number of defined objects - asl ; 2 * 2 +; asl ; 2 * 2 tax ; 2 use as index ShapeLoop: - dex ; 2 +; dex ; 2 dex ; 2 bmi Transform ; 2+ done _codeAR1: @@ -896,9 +968,9 @@ _codeAR1: cmp #$02 ; 2 2 or 3? bcc ShapeLoop ; 2+ no, move on stx SavedShapeIndex ; 4 - txa ; 2 - lsr ; 2 - tax ; 2 +; txa ; 2 +; lsr ; 2 +; tax ; 2 lda FirstLineIndex,X; 4+ sta FIRST_LINE ; 3 lda LastLineIndex,X ; 4+ @@ -916,10 +988,10 @@ NoLines1: ;=============================== Transform: lda NumObjects ; 4 - asl ; 2 +; asl ; 2 tax ; 2 TransLoop: - dex ; 2 +; dex ; 2 dex ; 2 bmi DrawNew ; 2+ _codeAR2: @@ -950,9 +1022,9 @@ _xrotAR: lda XROT_arr,X ; 4+ sta xrot ; 3 stx SavedShapeIndex ; 4 save this off - txa ; 2 - lsr ; 2 convert to 1x index - tax ; 2 +; txa ; 2 +; lsr ; 2 convert to 1x index +; tax ; 2 lda FirstPointIndex,X; 4+ sta FIRST_LINE ; 3 (actually first_point) lda LastPointIndex,X; 4+ @@ -981,10 +1053,10 @@ _syAR: DrawNew: lda NumObjects ; 4 - asl ; 2 +; asl ; 2 tax ; 2 L1ECE: - dex ; 2 +; dex ; 2 dex ; 2 bmi L1EF9 ; 2+ _codeAR3: @@ -993,9 +1065,9 @@ _codeAR3: cmp #$03 ; 2 beq L1ECE ; 2+ yup, no draw stx SavedShapeIndex ; 4 save index - txa ; 2 - lsr ; 2 convert it back to 1x index - tax ; 2 +; txa ; 2 +; lsr ; 2 convert it back to 1x index +; tax ; 2 lda FirstLineIndex,X; 4+ draw all the lines in the shape sta FIRST_LINE ; 3 lda LastLineIndex,X ; 4+ @@ -1020,7 +1092,9 @@ L1EF9: ;******************************************************************************* RESET: - jsr Setup ; 6 sets A=0, Y=$1E +; jsr Setup ; 6 sets A=0, Y=$1E + lda #$0 + ldy #$F _codeAR4: sta CODE_arr,y ; 5 zero out CODE% dey ; 2 @@ -1082,6 +1156,30 @@ HI_RES: ; If you generate the module for Integer BASIC, this is the entry point for ; MISSILE (7993). + +CODE_arr: + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +X_arr: + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +Y_arr: + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +XROT_arr: + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +YROT_arr: + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +ZROT_arr: + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +SCALE_arr: + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +SX_arr: + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +SY_arr: + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + + + +.if 0 + Setup: lda BAS_ARYTAB ; 3 CODE% is at +$0008 clc ; 2 @@ -1164,6 +1262,7 @@ Setup: ldy #$1e ; 2 Set A and Y for RESET lda #$00 ; 2 rts ; 6 +.endif ; Junk; pads binary to end of page. .align $0100