bson@rice-chex.ai.mit.edu (Jan Brittenson) (11/16/90)
OK, here's my sprite drawer if you're interested. First a warning: this program will happily scramble your memory if given improper arguments. This generally means, but is not limited to, specifying coordinates outside the GROB. I will post the source code only. It is copyrighted by me, but you are hereby granted the right to use it for whatever non-commercial purposes you desire. For other purposes, consult me. Of course no responsibility or liability is taken. You're on your own here, should your beloved HP go down in flames, or you end up requiring life-long psychiatric care. SPRITE The sprite drawer initially reads four arguments off the stack: 4: destination.grob 3: row.short 2: col.short 1: sprite.string The row (Y) and column (X) are read into the Y and X registers, and the IP is set to the first nibble of the string, where execution will begin. The drawer maintains the following of registers: reg bits description X 12 - current column (0 is leftmost) 0-#fff Y 12 - current row (0 is uppermost) 0-#fff A 4 - angle 0-15 C 20 - collision counter 0-#fffff G 3 - current GROP, graphical operator, 0,1,2,4 F 3 - flags F.C, F.PP, F.PC IP 20 - current sprite address [Initial values: A=0, C=0, F.*=0, G=1] The F register is divided into three bits: F.C - collision detection off/on (0/1) F.PP - push point on stack when done off/on (0/1) F.PC - push collision counter on stack when done off/on (0/1) The A register contains the angle offset. It consitutes an offset added to all drawing and moving commands to deduct the effective angle. The angle is one of 16: 5 3 6 4 2 7 \|/ 1 8- -0 9 /|\ f a c e b d The odd angles are halfway between the even ones. The angle 5 is halfway between 4 and 6 (112.5 degrees). During execution the following commands make sense; all others are ignored. opcode mnemonic decriptions 1g G=g Set GROP to g: 1=BIS, 2=XOR, 4=BIC 2 F.C=1 Enable collision detection 3 F.C=0 Disable collision detection 4na MOVE n,a Move n (1-15) pixels, angle a Enna MOVE nn,a Move nn (1-255) pixels, angle a 7a DRAW 1,a Draw 1 pixel, angle a 8na DRAW n,a Draw n (1-15) pixels, angle a Dnna DRAW nn,a Draw nn (1-255) pixels, angle a B SETPT Set pixel at current X,Y 90 F.PC=1 Push collision counter when done 91 F.PP=1 Push Y,X when done 92 POP XY Pop new X,Y off stack 93 POP A Pop new A off stack c END The drawing and moving commands start at the current point, and move the specified number of pixels in the specified direction. The drawing commands set the pre-draw current point, and end up with the current point set to the first pixel following the last pixel set. In other words, the command DRAW 3,2 (822) will do the following: O 4: move one pixel and leave the point / * 3: move one pixel and set the point / * 2: move one pixel and set the point / * 1: set the current point The pixel is set according to the current GROP (G register). The same is true for SETPT as well, which simply sets the current point without moving. If collision detection is enabled, the point is tested and the collision counter updated before any graphical operation is applied. When drawing, collision is checked for the first and up to but not including the final point (i.e. 1-3 would be checked in the example above). SETPT always checks for collision detection if enabled. I hope this brief explanation is sufficient. O / \/ /\ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ O \ ;; + ;; SPRITE -- HP-48 sprite drawer ;; ;; ;; In: ;; 4:dest.grob ;; 3:row.short ;; 2:col.short ;; 1:sprite.string ;; ;; Out: ;; any or none ;; ;; ;; Register usage: ;; ;; d1 - current GROB address ;; d0 - current sprite address ;; r0.x - GROB cols (X max + 1) ;; r1.x - GROB rows (Y max + 1) ;; r3.x - current col (X), 0-#fff (0-4095) ;; r4.x - current row (Y), 0-#fff (0-4095) ;; r3.3 - rotational angle 0-15 ;; b.s - current mask (1,2,4,8) ;; r3.4:8 - collision counter ;; r3.9 - current GROP: ;; 1 - OR ;; 2 - XOR ;; 4 - bic ;; ;; r3.13:15 - GROB row width, in nibbles ;; ;; d.s - current flags: ;; 0 - collision detection enabled ;; 1 - exit ;; 2 - push collision counter when done ;; 3 - push end point when done ;; ;; r2.a - relocation constant ;; ;; ;; bson@ai.mit.edu, Nov 8, 1990 ;; ;; Copyright (c) 1990, Jan Brittenson ;; ;; This program is distributed in the hope that it will be useful, but ;; WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. You may freely ;; use, modify, and redistribute it for any noncommercial purpose, given ;; that this notice is not removed or changed. ;; ;; ;;-- get2shorts=#3f5d getshort=#6641 restore_regs=#67d2 save_regs=#679b error_too_few_args=#18cc7 error_bad_arg_type=#18cb7 pushshort_r0=#6537 pushshort_r0r1=#6529 rplret=#71be data.b 'H' data.b 'P' data.b 'H' data.b 'P' data.b '4' data.b '8' data.b '-' data.b 'D' data.a #2dcc begin: data.a end-begin sprite: call.3 reloc reloc: pop.a c move.w c,r2 ; R2 = logical reloc move.a @d1,c brnz.a c,L_1000 jump.3 sprite_insuff_args L_1000: add.a 5,d1 inc.a d swap.a c,d1 push.a c move.a @d1,a move.p5 #2a2c,c ; String breq.a c,a,L_1001 jump.3 sprite_bad_argtype L_1001: add.a 10,d1 ; Skip type and length pop.a c swap.a c,d1 push.a c ; Push string addr call.3 pop_xy ; Get row, col from stack move.a @d1,c brnz.a c,L_1002 jump.3 sprite_insuff_args L_1002: add.a 5,d1 inc.a d swap.a c,d1 push.a c move.a @d1,a move.p5 #2b1e,c ; GROB breq.a c,a,L_1003 jump.3 sprite_bad_argtype L_1003: add.a 10,d1 move.a @d1,a ; Get GROB height move.w a,r1 ; R1.x = GROB height add.a 5,d1 move.a @d1,a ; Get GROB width move.w a,r0 ; R0.x = GROB width pop.a c swap.a c,d1 push.a c call.a save_regs pop.a c move.a c,d1 add.a 5,d1 ; D1 = GROB data pop.a c move.a c,d0 ; D0 = string address call.3 sprite_init ; Initialize reentrant variables ; Main loop sprite_main: call.3 sprite_exec1 ; Execute one command rln.w d move.p d,c rrn.w d brbc 1,c,sprite_main ; Exit move.s d,c move.w c,r0 call.a restore_regs move.w r0,c rln.w c push.a c move.a c,a brbc 3,a,L_301 ; Don't push final point clr.a c move.x r4,c move.w c,r0 move.x r3,c move.w c,r1 call.a pushshort_r0r1 ; Push in order: row, col L_301: pop.a c move.a c,a brbc 2,a,L_300 ; Don't push collision counter move.w r3,c rrn.w c rrn.w c rrn.w c rrn.w c move.w c,r0 call.a pushshort_r0 ; Push: coll count L_300: jump.a rplret ; Various error messages sprite_bad_argtype: jump.a error_bad_arg_type sprite_insuff_args: jump.a error_too_few_args ; Get X and Y from stack pop_xy: call.a get2shorts ; C=lev1.short, A=lev2.short move.x a,r3 ; R3.x = cur row move.x c,r4 ; R4.x = cur col ret ; Reset to default values, except for variables from stack. sprite_init: move.w r2,a move.p5 grob_base,c add.a a,c swap.a c,d1 move.a c,@d1 ; Save GROB addr in grob_base swap.a c,d1 move.w r3,a ; Angle, coll ctr, GROP, all = 0 clr.w c or.x a,c move.w r0,a ; A.x = GROB cols brbs 0,a,L_1 ; Not a byte brbs 1,a,L_1 ; Not a byte brbc 2,a,L_2 ; A byte L_1: add.x 8,a ; Adjust to next byte L_2: srb.x a ; Turn into nibble count srb.x a clrb 0,a ; Always even # of nibbles rrn.w a ; Move into nibbles 13,14,15 rrn.w a rrn.w a move.1 13,p or.p a,c inc.1 p or.p a,c inc.1 p or.p a,c move.w c,r3 move.1 0,p move.w r4,a ; A.x = row#, C.x = col# call.3 sprite_mapxy ; C.a = addr, A.0 = mask rrn.w a move.s a,b ; B.s = cur mask swap.a a,d1 add.a c,a swap.a a,d1 ; D1 = cur addr move.p1 1,c ; Default GROP = OR call.3 set_grop clr.s d ; No flags enabled ret ; Map row (A.x) and col (C.x) to nibble offset and mask. ; Set C.a to addr, and A.1 to mask. sprite_mapxy: push.a c move.x a,c move.x c,d ; D.x = row # move.w r3,c clr.b c rln.w c rln.w c rln.w c ; C.a = row size in nibbles clr.a b brz.x d,L_4 L_3: add.a c,b dec.x d brnz.x d,L_3 L_4: pop.a c clr.a d move.x c,d ; D.a = col # srb.x d srb.x d ; D.a = nibble # swap.a c,d add.a c,b ; B.a = addr swap.a c,d clrb 2,c clrb 3,c move.p c,d ; D.a = col # clr.a a inc.a a ; A.a = 1 brz.p d,L_5 L_6: add.a a,a dec.p d brz.p d,L_6 L_5: move.a b,c ; C.a = nibble # ret ; Set current pixel spr_x_setpix: call.3 get_grop ; C.0 = GROP move.s d,c ; C.1 = GROP, C.0 = run flags rln.w c brbc 0,c,L_428 ; No collision detection - skip move.s @d1,c ; C.15 = Current GROB nibble and.s b,c ; Test if collision brz.s c,L_428 ; Nope - skip move.w r3,a rrn.w a rrn.w a rrn.w a rrn.w a inc.x a ; Yep - increment counter rln.w a rln.w a rln.w a rln.w a move.w a,r3 L_428: move.s @d1,c ; C.15 = Current GROB nibble brbc 4,c,L_4281 ; GROP != OR or.s b,c ; C.15 = old OR mask brcc L_4299 L_4281: brbc 5,c,L_4282 ; GROP != XOR move.s b,a not.s a and.s c,a ; A.s = !B.s & C.s not.s c and.s b,c ; C.s = B.s & !C.s or.s a,c ; C.s = !B.s & C.s | B.s & !C.s brcc L_4299 L_4282: brbc 6,c,L_42991 ; GROP != BIC move.s b,a not.s a ; A.s = !mask and.s a,c ; C.s = !mask & C.s L_4299: move.s c,@d1 ; Write back L_42991: ret ; Execute one sprite command sprite_exec1: clr.a a move.p @d0,a ; A.a = nibble add.a 1,d0 move.p5 sprite_exec_table,c ; Jump indirect table. ; A.a = entry#, C = table offset spr_exec: move.a c,b move.w r2,c ; C.a = reloc add.a b,c ; C.a = addr move.a a,b ; B=entry# add.a a,a add.a a,a ; A *= 4 add.a b,a ; A *= (4+1) add.a c,a ; A = table addr swap.a a,d0 move.a @d0,c swap.a a,d0 move.a c,b ; B = code offset move.w r2,c add.a b,c ; C = code addr jump.a c ; END - terminate spr_x_end: rln.w d ; Get flag word move.p d,c setb 1,c ; Set bit #1 move.p c,d rrn.w d ; NOP - no operation spr_x_nop: ret ; ENA COLL - enable collision detection spr_x_ena_coll: rln.w d ; Get flag word move.p d,c setb 0,c ; Set bit 0 L_8: move.p c,d rrn.w d ret ; DIS COLL - disable collision detection spr_x_dis_coll: rln.w d ; Get flag word move.p d,c clrb 0,c ; Clear bit 0 brnz.s b,L_8 ; PUSH/POP - various PUSH/POP commands spr_x_push: clr.a a move.1 @d0,a ; Next nibble add.a 1,d0 move.p5 push_table,c jump.3 spr_exec ; Jump indirect ; PUSH COLL - push collision count when done spr_x_push_coll: setb 2,c ; Set bit 2 brnz.p c,L_8 ; PUSH POINT - push last point when done spr_x_push_pt: setb 3,c ; Set bit 3 brnz.p c,L_8 ; POP XY - Get new X and Y coordinates from stack spr_x_pop_xy: call.3 swap_regs call.3 pop_xy ; Pop row, col off stack call.3 swap_regs move.w r3,c ; C.x = col # move.w r4,a ; A.x = row # call.3 sprite_mapxy ; C.a = addr, A.0 = mask rrn.w a move.s a,b ; B.s = new mask push.a c ; Save GROB offset move.w r2,a ; A.a = reloc move.p5 grob_base,c add.a c,a ; C.a = &grob_base swap.a a,d1 move.a @d1,a ; A.a = grob_base pop.a c add.a c,a ; A.a = grob_base + new offset move.a a,d1 ; D1 = new GROB address ret ; POP ANGLE - Get new rotation from stack spr_x_pop_rot: call.3 swap_regs call.a getshort ; A.a = angle call.3 swap_regs move.1 3,p rln.w a rln.w a rln.w a ; A.3 = angle move.p a,r3 ; R3.3 = angle move.1 0,p ret ; MOVE n,m - move n (0-15) pixels, angle m spr_x_move: call.3 get_grop ; C.0 = GROP move.s d,c ; C.0 = run flags, C.1 = GROP rln.w c push.a c clrb 0,c ; Disable collision detection rrn.w c move.s c,d clr.p c call.3 set_grop ; GROP = 0 call.3 spr_x_drawn ; Draw with null GROP pop.a c ; C.1 = GROP, C.0 = run flags rrn.w c move.s c,d ; Restore run flags jump.3 set_grop ; Restore GROP ; MOVE nn,m - move nn (0-255) pixels, angle m spr_x_movenn: call.3 get_grop ; C.0 = GROP move.s d,c ; C.0 = run flags, C.1 = GROP rln.w c push.a c clrb 0,c ; Disable collision detection rrn.w c move.s c,d clr.p c call.3 set_grop ; GROP = 0 call.3 spr_x_drawnn ; Draw with null GROP pop.a c ; C.1 = GROP, C.0 = run flags rrn.w c move.s c,d ; Restore run flags jump.3 set_grop ; Restore GROP ; Adjust row and column according to row/col increments. ; Row: A.x, Col: C.x, Adjustment: B.x, Increments: A.3:4 ; Uses: D.x adjrowcol: swap.a c,a move.x c,d swap.a c,a swap.a c,b push.a c swap.a b,c rrn.w a brbc 12,a,L_222 ; Row is not half-angle srb.x b ; Row is half-angle L_222: brbc 8,a,L_20 ; No row movement, adjust col brbs 9,a,L_21 ; Moving up neg.x b ; Up: use negative offset L_21: swap.a c,d add.x b,c ; Down: add to row number swap.a c,d L_20: swap.a c,b pop.a c push.a c swap.a c,b brbc 14,a,L_223 ; Col is not half-angle srb.x b ; Col is half-angle L_223: brbc 10,a,L_221 ; No col adjustment brbc 11,a,L_23 ; Moving left neg.x b ; Left: use negative col offset L_23: add.x b,c ; Right: add to col # L_221: swap.a c,b pop.a c swap.a c,b rln.w a swap.a c,d move.x c,a swap.a c,d ret ; Fetch nibble and add to rotational angle. ; Map to incrementation bits, and store them in A.3:4 ; Clears A.x and wrecks C.w spr_fetch_angle: move.1 0,p clr.a a move.1 @d0,a ; A.a = angle add.a 1,d0 move.w r3,c ; C.3 = rotation srn.a c srn.a c srn.a c add.p c,a add.a a,a ; Nibbles to bytes move.w r2,c add.a c,a move.p5 angle_table,c add.a a,c ; C.a = byte pointer swap.a c,d0 move.b @d0,a ; A.b = angle bits swap.a c,d0 sln.w a sln.w a sln.w a ; A.3:4 = angle bits ret ; GROP - set GROP spr_x_grop: move.1 9,p move.w r3,a move.p @d0,a ; A.9 = nibble move.p1 3,c ; C.9 = 3 and.p c,a ; Mask low 2 bits move.w a,r3 move.1 0,p ret ; General GROP = C.0 ; Trashes C.9 set_grop: move.1 c,0,p ; P = C.0 move.1 p,c,9 ; C.9 = P move.1 9,p move.p c,r3 ; R3.9 = GROP move.1 0,p ret ; General C.0 = GROP ; Trashes C.9 get_grop: move.1 9,p move.p r3,c ; C.9 = GROP move.1 c,9,p ; P = C.9 move.1 p,c,0 ; C.0 = P move.1 0,p ret ; DRAW nn,m - draw nn pixels long sprite spr_x_drawnn: clr.x c move.b @d0,c add.a 2,d0 move.x c,b ; B.x = C.x = sprite length brcc spr_x_draw_b ; DRAW1 - draw 1-pixel sprite spr_x_draw1: move.p1 1,c ; One pixel long brnz.p c,spr_x_draw_main ; Go draw ; DRAWN - draw n-pixel sprite spr_x_drawn: move.1 @d0,c ; # of pixels of length add.a 1,d0 ; Draw sprite of length C.1. Get nibble indicating angle and ; add basic rotation. In the draw loop, registers are ; used as follows: ; ; B.x - length (1-10h) ; D.a - row width, in nibbles (from R3.13:15) ; A.x - collision counter ; A.3 - increment flags ; 12 - row movement on/off (0/1) ; 13 - row movement up/down (0/1) ; 14 - col movement on/off (0/1) ; 15 - col movement left/right (0/1) ; C.0 - D.s run flags ; C.1 - GROP ; C.2 - half-angle increment flags ; 8 - row is half-angle ; 9 - row half-angle toggle ; 10 - col is half-angle ; 11 - col half-angle toggle ; ; The rest of the registers are used in their global role. E.g. ; B.s is the current mask. ; spr_x_draw_main: move.1 0,p clr.x b move.p c,b ; B.x = # of pixels ; Alternative entry, B.x = sprite length spr_x_draw_b: call.3 spr_fetch_angle ; A.3:4 = angle bits, A.x = 0 move.w r3,c ; C.x = col number move.x r4,a ; A.x = row number call.3 adjrowcol ; Adjust C.x,A.x by B.x, via A.3:4 move.x c,r3 ; Update col move.x a,r4 ; Update row move.w r3,c rrn.w c rrn.w c rrn.w c rrn.w c move.x c,a ; A.x = collision counter move.w r3,c clr.b c rln.w c rln.w c rln.w c move.a c,d ; D.a = width, in nibbles brbs 13,a,L_311 ; Moving down, if any neg.a d ; Use negative row width if up L_311: call.3 get_grop ; C.0 = GROP move.s d,c rln.w c ; C.0 = run flags, C.1 = GROP swap.a c,a move.1 c,4,p swap.a c,a move.1 p,c,2 ; C.xs = A.4 = half-angle flags move.1 0,p ; Main draw loop. ; Alter pixel, move, and iterate. sprite_draw: brbc 0,c,L_28 ; No collision detection - skip move.s @d1,c ; C.15 = Current GROB nibble and.s b,c ; Test if collision brz.s c,L_28 ; Nope - skip inc.x a ; Yep - increment counter L_28: move.s @d1,c ; C.15 = Current GROB nibble brbc 4,c,L_281 ; GROP != OR or.s b,c ; C.15 = old OR mask brcc L_299 L_281: brbc 5,c,L_282 ; GROP != XOR move.s b,a not.s a and.s c,a ; A.s = !B.s & C.s not.s c and.s b,c ; C.s = B.s & !C.s or.s a,c ; C.s = !B.s & C.s | B.s & !C.s brcc L_299 L_282: brbc 6,c,L_2991 ; GROP != BIC move.s b,a not.s a ; A.s = !mask and.s a,c ; C.s = !mask & C.s L_299: move.s c,@d1 ; Write back L_2991: brbc 12,a,L_30 ; No row movement - move col brbc 8,c,L_100 ; Row is not half-angle, simply add brbs 9,c,L_101 ; Move and clear toggle setb 9,c ; Move next time around brcc L_30 ; Move col L_101: clrb 9,c L_100: swap.a c,d1 add.a d,c ; Add one row up or down swap.a c,d1 L_30: brbc 14,a,L_32 ; No col movement - continue brbc 10,c,L_102 ; Col is not half-angle brbs 11,c,L_103 ; Move and clear toggle setb 11,c ; Move col next time around brcc L_32 ; Iterate L_103: clrb 11,c ; Don't move col next time around L_102: brbc 15,a,L_33 ; Moving left add.s b,b ; Moving right - shift mask left brnz.s b,L_32 ; Still within nibble - continue add.a 1,d1 ; Outside - next nibble inc.s b ; Set mask to 1 brnz.s b,L_32 ; Continue L_33: srb.s b ; Moving left - shift mask right brnz.s b,L_32 ; Still within nibble - continue inc.s b ; Set mask to 1 add.s b,b ; ...2 add.s b,b ; ...4 add.s b,b ; ...8 sub.a 1,d1 ; And move to prev nibble L_32: dec.x b ; Decrement pixel counter brz.x b,L_35 jump.3 sprite_draw ; Continue drawing ; Restore/update global registers and return L_35: move.1 3,p ; Zero extend coll ctr to 5 nibbles clr.p a inc.1 p clr.p a move.w r3,c rrn.w c rrn.w c rrn.w c rrn.w c clr.a c or.a a,c rln.w c rln.w c rln.w c rln.w c move.w c,r3 ; R3.4:8 = collision counter move.1 0,p ret ; Swap registers with their saved contents. ; Wrecks C.a. TOH=#705b0 TOS=#70579 FP=#70574 Free_size=#7066e swap_regs: move.a a,c push.a c swap.a c,d0 move.5 TOH,d0 move.a @d0,a move.a c,@d0 move.5 TOS,d0 move.a @d0,c swap.a c,d1 move.a c,@d0 move.5 FP,d0 move.a @d0,c swap.a c,b move.a c,@d0 move.5 Free_size,d0 move.a @d0,c swap.a d,c move.a c,@d0 move.a a,d0 pop.a c move.a c,a ret ; Jump table for first nibble sprite_exect: sprite_exec_table=sprite_exect-reloc data.a spr_x_nop-reloc data.a spr_x_grop-reloc data.a spr_x_ena_coll-reloc data.a spr_x_dis_coll-reloc data.a spr_x_move-reloc data.a spr_x_nop-reloc data.a spr_x_nop-reloc data.a spr_x_draw1-reloc data.a spr_x_drawn-reloc data.a spr_x_push-reloc data.a spr_x_nop-reloc data.a spr_x_setpix-reloc data.a spr_x_end-reloc data.a spr_x_drawnn-reloc data.a spr_x_movenn-reloc data.a spr_x_nop-reloc ; Jump table for second nibble of 9x group (PUSH/POP) sprite_pusht: push_table=sprite_pusht-reloc data.a spr_x_push_coll-reloc data.a spr_x_push_pt-reloc data.a spr_x_pop_xy-reloc data.a spr_x_pop_rot-reloc data.a spr_x_nop-reloc data.a spr_x_nop-reloc data.a spr_x_nop-reloc data.a spr_x_nop-reloc data.a spr_x_nop-reloc data.a spr_x_nop-reloc data.a spr_x_nop-reloc data.a spr_x_nop-reloc data.a spr_x_nop-reloc data.a spr_x_nop-reloc data.a spr_x_nop-reloc ; Sprite angle-to-incrementation map. ; The angle entries are bit maps interpreted as follows: ; ; 0a0bcdef ; ; Where ; a = Column is half-angle ; b = Row is half-angle ; c = Left/right ; d = Column movement off/on ; e = Up/down ; f = Row movement off/on ; sprite_anglet: angle_table=sprite_anglet-reloc data.2 #0c ; 0 (0) data.2 #1d ; 1 (22.5) data.2 #0d ; 2 (45) data.2 #4d ; 3 (67.5) data.2 #01 ; 4 (90) data.2 #45 ; 5 (112.5) data.2 #05 ; 6 (135) data.2 #15 ; 7 (157.5) data.2 #04 ; 8 (180) data.2 #17 ; 9 (202.5) data.2 #07 ; a (225) data.2 #47 ; b (247.5) data.2 #03 ; c (270) data.2 #4f ; d (292.5) data.2 #0f ; e (315) data.2 #1f ; f (337.5) ; Various variables grob_basev: grob_base=grob_basev-reloc data.a 0 ; GROB base address end: