;============================================================================ ; 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" entry: jsr reset jsr make_tables ; 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 ; SCALE[0]=15:XROT[0]=2:YROT[0]=5:ZROT[0]=0 ; SCALE[1]=15:XROT[1]=2:YROT[1]=5:ZROT[1]=0 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 ;=========================================================== ; Note that all tables are page-aligned for performance. * ;=========================================================== .include "shapes.s" .align $100 .include "math_constants.s" .include "scale_constants.s" make_tables: ; Make hires row lookup table using HPOSN ; From the Applesoft ROM ldx #0 hgr_table_loop: ldy #0 txa jsr HPOSN ; (Y,X),(A) (values stores in HGRX,XH,Y) ldx HGR_X lda GBASL sta YTableLo,X lda GBASH sta YTableHi,X inx cpx #192 bne hgr_table_loop ;===================== ; Make Div7 table lda #0 tax tay div7_table_loop: sta Div7Tab,X iny cpy #7 bne not_7 ldy #0 clc adc #1 not_7: inx bne div7_table_loop ; ; 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.) ; lda #1 ldx #0 ldy #0 bittab_table_loop: sta HiResBitTab,X asl iny cpy #7 bne again_not_7 ldy #0 lda #1 again_not_7: inx bne bittab_table_loop rts ;==================================== ; DrawLineList ;==================================== DrawLineList: ; ; 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. ; ; Draw code calls here. Since we're configured for XOR mode, ; this just jumps to the exclusive-or version. 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 ; 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 sty _0F_or_11_1+2 ; 4 sty _0F_or_11_2+2 ; 4 sty _0F_or_11_3+2 ; 4 rts ; 6 ; 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. ; 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 * ;******************************************************************************* CRUNCH: ;============================== ; First pass: erase old shapes ;============================== ldx #NumObjects ; 2 number of defined objects ShapeLoop: 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 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 jmp ShapeLoop ; 3 ...always ;=============================== ; Second pass: transform shapes ;=============================== Transform: ldx #NumObjects ; 2 TransLoop: 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 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: ldx #NumObjects ; 2 L1ECE: 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 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 ; + enables display of the primary hi-res page ;=============================================== ;=============================================== reset: ; lda #$0 ; ldy #$F ;zero_code_loop: ; sta CODE_arr,y ; 5 zero out CODE ; dey ; 2 ; bpl zero_code_loop ; 3+ jsr CLEAR ; 6 jsr SwapPage ; 6 jsr SwapPage ; 6 rts ; 6 ;******************************************************************************* ; CLEAR/CLR% entry point * ; * ; Clears both hi-res pages. * ;******************************************************************************* ; Clear variables 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 ; size these as appropriate ; we are assuming only 2 shapes here, can be up to 16 CODE_arr: .byte $00,$00 X_arr: .byte 127,20 Y_arr: .byte 96,30 XROT_arr: .byte 2,2 YROT_arr: .byte 5,5 ZROT_arr: .byte 0,0 SCALE_arr: .byte 15,15 SX_arr: .byte $00,$00 SY_arr: .byte $00,$00 ; layout: ; ; 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 = $6000 ; Computed Y coordinate, set 0. YCoord0_0F = $6100 ; Computed X coordinate, set 1. XCoord1_10 = $6200 ; Computed Y coordinate, set 1. YCoord1_11 = $6300 ; hgr lookup YTableLo = $6400 YTableHi = $6500 Div7Tab = $6600 HiResBitTab = $6700