ON ERROR E$=REPORT$+" at line "+STR$(ERL):ON ERROR OFF:ERROR 0,E$ : frequency = 50:REM cs between explosions size = 150:REM Number of particles per explosion max_particles = 1000:REM Maximum number of particles to allow on screen : REM Offsets to various things in our workspace : table_number = 0:REM Number of 'active' particles in table table_data = 4:REM Offset to number table_entry_size = 20:REM Size of each particle (in memory) table_entry_x = 0:REM X position table_entry_y = 4:REM Y position table_entry_xv = 8:REM X velocity table_entry_yv = 12:REM Y velocity table_entry_col = 16:REM Colour (0 means particle is inactive) : memory_requested = 20480:REM 20k should be enough : DIM code 8192 FOR pass=12 TO 14 STEP 2 P%=0:O%=code:L%=code+8192 [OPT pass : ; ; First we need the standard 'Out To Lunch' header so that OTL knows ; where to find the routines it needs. ; B DisplayInfo B DisplayStartUp B DisplayInit B DisplayPoll EQUB 10 EQUS "Out to Lunch display" EQUB 10 EQUS "Fireworks" EQUB 10 EQUS "0.01" EQUB 10 EQUS "(c) Matthew Bloch, 1997" EQUB 10 ALIGN : .DisplayInfo MOV R7,#2 ; Bit set to clear screen to black MOV R8,#memory_requested MOV R9,#256 ; 256 bytes stack MOVS PC,R14 : .DisplayStartUp MOVS PC,R14 : .DisplayInit STMFD R13!,{R0-R3,R12,R14} SWI "OS_ReadMonotonicTime" STR R0,rand_seed1 ; Seed random number generator SWI &100 + 26 ; Reset the graphics windows LDR R0,[R12,#8] ADD R12,R12,R0 ; R12 now points to our workspace ADR R0,screen_vars ADR R1,window_right SWI "OS_ReadVduVariables" ; Read size of screen, colours etc. LDR R1,screen_bpp_log2 CMP R1,#3 BEQ skip_adjust_window : ; ; If we're dealing with a 256-colour mode, the graphics windows need to be ; specified in terms of 'real' co-ordinates. Otherwise, we need to use the ; strange operating system 'external' co-ordinates, which the OS_ calls use ; to draw things on the screen with. ; LDR R1,screen_xeig LDR R0,window_right MOV R0,R0,LSL R1 STR R0,window_right LDR R1,screen_yeig LDR R0,window_top MOV R0,R0,LSL R1 STR R0,window_top .skip_adjust_window : MOV R0,#0 STR R0,last_explosion ; So we get an explosion straight away : STR R0,[R12,#table_number] MOV R0,#0 MOV R1,#0 MOV R2,#0 MOV R3,#0 MOV R4,#0 ADD R5,R12,#table_data MOV R6,#max_particles .wipe_table_loop STMIA R5!,{R0-R4} ; Wipe workspace with zeroes SUBS R6,R6,#1 BNE wipe_table_loop LDMFD R13!,{R0-R3,R12,PC}^ : .DisplayPoll STMFD R13!,{R1-R3,R12,R14} LDR R0,[R12,#8] ADD R12,R12,R0 ; R12 now points to our workspace : ; ; Check whether we need to create another explosion ; SWI "OS_ReadMonotonicTime" LDR R1,last_explosion SUB R1,R0,R1 CMP R1,#frequency STRGE R0,last_explosion BLGE new_explosion : ; ; Call either the direct draw routine (for 256-colour modes only) or the ; slower draw routine which works in any screen mode ; LDR R0,screen_bpp_log2 CMP R0,#3 BLEQ draw_particles_direct BLNE draw_particles : MOV R0,#0 ; Return zero to OTL (i.e. no error) LDMFD R13!,{R1-R3,R12,PC}^ : ; ; This routine treats all the table co-ordinates as suitable for passing to ; OS_ calls, and is slower than the direct draw routine below ; .draw_particles ; R12 > workspace STMFD R13!,{R0-R8,R12,R14} MOV R0,#19 SWI "OS_Byte" ; Wait for vsync (smooths the animation) MOV R6,#max_particles ; R6 counts number of particles to draw ADD R7,R12,#table_data ; R7 > next particle to draw .draw_loop LDMIA R7,{R1-R5} ; Load x,y,xv,yv,colour at once CMP R5,#0 BEQ end_draw_loop ; Don't draw if colour = 0 (i.e. erased) : ; ; Note how we're using the stack to preserve registers while we call a SWI ; quickly, which is much neater than MOVing their contents to other registers ; STMFD R13!,{R1-R4} MOV R0,#0 MOV R3,#0 MOV R4,#0 SWI "ColourTrans_SetGCOL" ; Select black LDMFD R13!,{R1-R4} STMFD R13!,{R1-R2} MOV R0,#69 MOV R1,R1,LSR#12 ; R1 = (R1 >> 12) ... MOV R2,R2,LSR#12 ; ... LSR#12 cuts off 'decimal' part SWI "OS_Plot" ; Erase previous position of particle LDMFD R13!,{R1-R2} : ADD R1,R1,R3 ADD R2,R2,R4 ; Add velocities to position SUB R4,R4,#128 ; Add gravity to Y velocity CMP R2,#0 BGE dont_erase_particle : ; ; This section of code erases a particle from the particle table by changing ; its colour to zero (this means it will be ignored by the plotting code). This ; is only executed if the particle has gone off the *bottom* of the screen ; MOV R5,#0 STR R5,[R7,#table_entry_col] LDR R0,[R12,#table_number] SUB R0,R0,#1 STR R0,[R12,#table_number] B end_draw_loop .dont_erase_particle : ; ; Store altered x, y, xv, yv, colour back in table with one instruction ; STMIA R7,{R1-R5} : STMFD R13!,{R1-R2} MOV R0,R5 MOV R3,#0 MOV R4,R0 SWI "ColourTrans_SetGCOL" ; Select colour of particle LDMFD R13!,{R1-R2} MOV R0,#69 MOV R1,R1,LSR#12 MOV R2,R2,LSR#12 SWI "OS_Plot" ; Draw particle .end_draw_loop ADD R7,R7,#table_entry_size SUBS R6,R6,#1 BNE draw_loop ; Loop round, draw next particle LDMFD R13!,{R0-R8,R12,PC}^ : ; ; This routine draws the particles directly onto the screen in 256-colour ; modes, and if *far* faster than the other one which uses OS_Plot. There is ; some code duplicated, but not really enough to justify making any of it into ; separate subroutines. ; .draw_particles_direct STMFD R13!,{R0-R8,R12,R14}^ MOV R0,#19 SWI "OS_Byte" ; Wait for vsync (smooths the animation) LDR R0,[R12] MOV R6,#max_particles ; R6 counts number of particles to draw ADD R7,R12,#table_data ; R7 > next particle to draw LDR R8,screen_base .draw_direct_loop LDMIA R7,{R1-R5} ; Load x,y,xv,yv,colour at once CMP R5,#0 BEQ end_draw_direct_loop ; Ignore if particle has been erased : ; ; This part erases the particle's previous position, but look how the screen ; address is calculated with the MLA instruction... the address of the byte to ; poke will be at (screen_base_address + (y * line_length) + x) ; STMFD R13!,{R1-R2} LDR R0,window_right ADD R0,R0,#1 MOV R1,R1,LSR#12 MOV R2,R2,LSR#12 MLA R0,R2,R0,R1 ADD R0,R0,R8 ; R0 = address to plot black dot at MOV R1,#0 STRB R1,[R0] ; Plot it (quickly) LDMFD R13!,{R1-R2} : ; ; Here, we need to move the position and velocity of the particle on accordingly ; BUT we need to check the particle against every side of the screen, since ; clipping isn't handled for us by the OS_Plot calls. ; ADD R1,R1,R3 ADD R2,R2,R4 ADD R4,R4,#128 LDR R0,window_top ; Now bottom MOV R0,R0,LSL#12 CMP R2,R0 BGE erase_particle_direct CMP R2,#0 BLT erase_particle_direct LDR R0,window_right MOV R0,R0,LSL#12 CMP R1,R0 BGE erase_particle_direct CMP R1,#0 BLT erase_particle_direct B dont_erase_particle_direct .erase_particle_direct ; Copied from above MOV R5,#0 STR R5,[R7,#table_entry_col] LDR R0,[R12,#table_number] SUB R0,R0,#1 STR R0,[R12,#table_number] B end_draw_direct_loop .dont_erase_particle_direct : STMIA R7,{R1-R5} ; Store altered values back again LDR R0,window_right ADD R0,R0,#1 MOV R1,R1,LSR#12 MOV R2,R2,LSR#12 MLA R0,R2,R0,R1 ADD R0,R0,R8 STRB R5,[R0] ; Plot pixel as we did a second ago .end_draw_direct_loop ADD R7,R7,#table_entry_size SUBS R6,R6,#1 BNE draw_direct_loop ; Again, see the above routine LDMFD R13!,{R0-R8,R12,PC}^ : ; ; This is called every so often (see frequency variable in first line of this ; source) to create a random new explosion. It finds space for 'size' particles ; in the table, and creates this number of particles eminating from the same ; point on the screen. If there isn't space in the table, it will do nothing ; .new_explosion ; R12 > workspace on entry STMFD R13!,{R0-R9,R14} ; ; First, check there's enough inactive particles for us to use, and if so, ; update the count and carry on... otherwise return immediately ; LDR R0,[R12,#table_number] ADD R0,R0,#size CMP R0,#max_particles LDMGTFD R13!,{R0-R9,PC} STR R0,[R12,#table_number] : MOV R6,#size ; R6 = particles left to create ADD R7,R12,#table_data ; R7 > current 'search' position : ; ; Choose a random X and Y position for all particles to start from ; BL random LDR R1,window_right MOV R1,R1,LSL#12 ; Decimal point BIC R0,R0,#&F0000000 .on_right CMP R0,R1 SUBGE R0,R0,R1 ; Subtract until we're in range BGE on_right MOV R8,R0 : BL random LDR R1,window_top MOV R1,R1,LSL#12 BIC R0,R0,#&F0000000 .on_top CMP R0,R1 SUBGE R0,R0,R1 BGE on_top MOV R9,R0 : ; ; This loop goes through the table, finding space to create the next particle. ; If we've got this far, there must be enough space to create however many ; dots we need to ; .create_dot_loop LDR R2,[R7,#table_entry_col] CMP R2,#0 ADDNE R7,R7,#table_entry_size BNE create_dot_loop : ; ; Store the x and y positions in the 'empty' table entry. ; STR R8,[R7,#table_entry_x] STR R9,[R7,#table_entry_y] : ; ; Choose a random velocity by selecting a value from our sine/cosine table ; and multiplying it up... we need the lookup table to avoid square explosions ; like in Swarm ;-) ; BL random BIC R1,R0,#&FF000000 BIC R1,R1,#&00FF0000 .degrees CMP R1,#360 SUBGE R1,R1,#360 BGE degrees ; R1 = Random no. between 0-359 ADR R5,velocities ADD R5,R5,R1,LSL#3 ; R5 = R5 + (angle(R1) * 8) LDR R2,[R5,#0] ; R2 = xv (unmultiplied) LDR R3,[R5,#4] ; R3 = yv (unmultiplied) MOV R4,R0,LSR#27 ; R4 = multiplier (max x32) MUL R2,R4,R2 MUL R3,R4,R3 ; Multiply velocity up STR R2,[R7,#table_entry_xv] ; Store velocities in table STR R3,[R7,#table_entry_yv] : BL random STR R0,[R7,#table_entry_col] ; Choose random colour : SUBS R6,R6,#1 BNE create_dot_loop ; Loop round, create next dot : LDMFD R13!,{R0-R9,PC} ; Restore registers and return : .random STMFD R13!,{R1-R2,R14} ; Random number routine LDR R0,rand_seed1 LDR R1,rand_seed2 TST R1,R1,LSR#1 MOVS R2,R0,RRX ADC R1,R1,R1 EOR R2,R2,R0,LSL#12 EOR R0,R2,R2,LSR#20 STR R0,rand_seed1 STR R1,rand_seed2 LDMFD R13!,{R1-R2,PC} ; Exits with R0 = random 32-bit number : .rand_seed1 EQUD 0 ; Space for random number generator .rand_seed2 EQUD 0 ; to store seed + temporary variables ; Value of OS_ReadMonotonicTime when the last explosion happened .last_explosion EQUD 0 ; List of vdu variables to read in DisplayInfo .screen_vars EQUD 11:EQUD 12:EQUD 4:EQUD 5:EQUD 149:EQUD 9:EQUD -1 .window_right ; Last x pixel number (multiplied by xeig for OS_ plotting) EQUD 0 .window_top ; Last y pixel number (multiplied by yeig for OS_ plotting) EQUD 0 .screen_xeig ; Number of bits to shift left to convert x/y ; 'internal' (i.e. 'real') to 'external' (OS) co-ordinates EQUD 0 .screen_yeig EQUD 0 .screen_base ; Base of screen memory EQUD 0 .screen_bpp_log2 ; ( 1 << this_value = bits per pixel for this mode) EQUD 0 .velocities ; Start of velocities table : ]:NEXT IF L%-O%<2880 THEN ERROR 0,"No room for velocities table" : REM This loop creates a lookup table for each compass direction, in degrees, REM so that, by picking a value from this table and multiplying it up, REM our explosions look circular. : FOR angle=0 TO 359 O%!0=COS(DEG(angle))*1024 O%!4=SIN(DEG(angle))*1024 O%+=8:NEXT : REM Save the file into the OTL displays folder, ready for testing SYS "OS_File",10,".Displays.Fireworks",&FFD,0,code,O% REM Fool OTL config program into thinking the display has changed, otherwise REM we have to re-configure it twice to test the Fireworks display. x%=OPENOUT(".Current"):BPUT#x%,"Black":CLOSE#x%