Compare commits

...

2 Commits
v0.1 ... main

Author SHA1 Message Date
Ben Bridle 51e1e85f54 Implement bucket-fill tool 2022-06-14 13:56:14 +12:00
Ben Bridle 3b00ba7ec7 Update README.md with more detail 2022-06-09 17:31:12 +12:00
2 changed files with 255 additions and 22 deletions

View File

@ -1,14 +1,20 @@
# Tungsten
A limited-palette 2D drawing program for Uxn, designed to be pleasant to use on the NintendoDS.
A low-tech drawing program for working with lower resolution, limited-palette images. Tungsten is currently in-development and is being written in UxnTAL for the Uxn virtual computer. The expected release date for a usable version 1.0 is August 2022.
Tungsten is designed for devices with a minimum screen resolution of 256x192. Devices with smaller screens will still be able to run Tungsten, but the edges of the user interface might extend off the screen.
## Functionality
Tungsten is currently lacking many vital pieces of functionality, including the ability to load or save images from disk.
## Assembling
The program is one monolithic TAL source file, assembled with [`uxnasm` from 100r](https://git.sr.ht/~rabbits/uxn/).
The assembler by default only allows 256 macro definitions in one program, but because Tungsten uses upwards of 400 macro definitions I've modified the assembler slightly to accomodate.
The 100r assembler by default only allows 256 macro definitions in one program, but because Tungsten uses upwards of 400 macro definitions I've had to patch the assembler slightly to accomodate.
To increase the macro definition limit on your machine, go to the file `src/uxnasm.c` and change the line `Macro macros[0x100]` to `Macro macros[0x400]`, and the line
`if(p.mlen == 0x100)` to `if(p.mlen == 0x400)`. Recompile `uxnasm` and you're all good to go.
To raise the macro definition limit on your machine, go to the assembler source file `src/uxnasm.c` and change the line `Macro macros[0x100]` to `Macro macros[0x400]`, and the line
`if(p.mlen == 0x100)` to `if(p.mlen == 0x400)`. Recompile `uxnasm` and you'll be able to assemble Tungsten without errors.

View File

@ -344,6 +344,12 @@
%PRINTF(%b) { ;print_byte_binary CALL }
%PRINTF(%b\s) { PRINTF(%b) PRINTF(\s) }
%PRINTF(%b\n) { PRINTF(%b) PRINTF(\n) }
%PRINTF(%?) { ;print_bool CALL }
%PRINTF(%?\s) { PRINTF(%?) PRINTF(\s) }
%PRINTF(%?\n) { PRINTF(%?) PRINTF(\n) }
%PRINTF(%?*) { ;print_bool_short CALL }
%PRINTF(%?*\s) { PRINTF(%?) PRINTF(\s) }
%PRINTF(%?*\n) { PRINTF(%?) PRINTF(\n) }
@ -352,6 +358,14 @@
|0100
@program_start ( -- )
( #0008 /V.CANVAS.WIDTH!
#0008 /V.CANVAS.HEIGHT!
COL_1 #0001 #0002
;fill_canvas_from_point CALL
HALT
)
( Initialise variables )
#0080 /V.CANVAS.WIDTH!
#0060 /V.CANVAS.HEIGHT!
@ -362,25 +376,230 @@
( ---------------------------------------------------------------------------- )
( C A N V A S S T U F F )
( C A N V A S P R O C E D U R E S )
( ALL OF THESE ROUTINES USE CANVAS-SPACE COORDINATES )
( Get the colour of the canvas pixel at the given coordinates. )
@get_canvas_pixel_colour ( x* y* -- colour )
;get_mask_and_address CALL LDA AND TO_BOOL RETURN
@set_canvas_pixel_colour ( colour x* y* -- )
;get_mask_and_address CALL STHk* LDA ROT ,&col_1 JCN ( mask byte | row_addr* )
&col_0 EOR ,&end JMP
&col_1 ORA
&end STHr* STA RETURN
@check_in_canvas_bounds ( x* y* -- in_bounds? )
/V.CANVAS.HEIGHT? LTH* STH /V.CANVAS.WIDTH? LTH* STHr AND RETURN
( Return 1 if pixel is in-bounds and the given colour, otherwise return 0 )
@check_canvas_pixel_colour ( colour x* y* -- is_colour? )
OVR* OVR* ;check_in_canvas_bounds CALL ,&cont JCN
( bail out ) POP* POP* POP #00 RETURN
&cont ;get_canvas_pixel_colour CALL EQU RETURN
( Note: These routines all use canvas-space coordinates )
@get_mask_and_address ( x* y* -- mask row_address* )
OVR* OVR* ;get_sprite_address CALL ( x* y* sprite_addr* )
SWP* MOD8* ADD* ( x* row_addr* )
STH* ;get_mask CALL STHr* RETURN
@get_sprite_address ( x* y* -- sprite_address* )
SWP* DIV8* SWP* DIV8* ( spritex* spritey* )
/V.CANVAS.WIDTH? CEIL8DIV8* ( sprite_x* sprite_y* sprite_width* )
MUL* ADD* MUL8* ;canvas_buffer ADD* ( sprite_addr* )
RETURN
@get_mask ( x* -- mask )
NIP MOD8 #0107 ROT SUB SHL4 SFT RETURN
@fill_canvas_from_point ( colour x* y* )
( Store starting values )
STH* ROT ;&col_p STA ( x* | y* )
DUP* ;&paint_x STA* ( x* | y* )
STHr* DUP* ;&paint_y STA* ( x* y* )
( Return if coords not in bounds )
;check_in_canvas_bounds CALL ( in_bounds? )
IF_FALSE RETURN ( -- )
( Find target colour )
;&paint_x LDA* ;&paint_y LDA* ( x* y* )
;get_canvas_pixel_colour CALL ( target_colour )
DUP ;&col_t STA ( target_colour )
( Return if target and paint colours are the same )
;&col_p LDA IF_EQUAL RETURN ( -- )
( Clear variables )
#0000 OVRk ;&mark1_a STA ;&mark2_a STA
;&backtrack STA ;&findloop STA ;&paint_d STA
( Move the painter to a wall to start )
&move_into_place ( -- )
;&front? CALL NOT ,&START JCN
;&move_forward CALL ,&move_into_place JMP
&MAIN_LOOP
;&move_forward CALL
( if right-pixel is inside, then )
;&right? CALL NOT ,&START JCN
( if backtrack is true and findloop is false and either front-pixel or left-pixel is inside, then )
;&backtrack LDA ;&findloop LDA NOT AND ;&front? CALL ;&left? CALL ORA AND NOT ,&no_findloop JCN
#01 ;&findloop STA
&no_findloop
;&turn_right CALL
&PAINT
;&move_forward CALL
&START
;&update_neighbour_wall_count CALL
;&wall_count LDA #04 EQU ,&check_1 JCN
( Orient the painter to face right along a wall )
&orient_right
;&front? CALL NOT ,&orient_left JCN
;&turn_right CALL ,&orient_right JMP
&orient_left
;&front? CALL ,&check_1 JCN
;&turn_left CALL ,&orient_left JMP
&check_1
;&wall_count LDA #01 NEQ ,&check_2 JCN
( If backtrack is True, findloop = True )
&1_1 ;&backtrack LDA NOT ,&1_2 JCN
#01 ;&findloop STA ;&MAIN_LOOP GOTO
( Else if findloop is True, make mark1 active )
&1_2 ;&findloop LDA NOT ,&1_3 JCN
#01 ;&mark1_a STA ;&MAIN_LOOP GOTO
( Else if front-left and back-left are both inside )
&1_3 ;&front_left? CALL ;&back_left? CALL AND NOT ;&MAIN_LOOP JCN*
#00 ;&mark1_a STA
;&paint_here CALL
;&PAINT GOTO
&check_2
;&wall_count LDA #02 NEQ ;&check_3 JCN*
( If back is not inside )
&2_1 ;&back? CALL ,&2_2 JCN
( If front-left is inside )
;&front_left? CALL NOT ;&MAIN_LOOP JCN*
#00 ;&mark1_a STA
;&paint_here CALL
;&PAINT GOTO
( If mark1 is not active )
&2_2 ;&mark1_a LDA ,&2_3 JCN
;&paint_x LDA* ;&mark1_x STA*
;&paint_y LDA* ;&mark1_y STA*
;&paint_d LDA ;&mark1_d STA
#0001 ;&mark1_a STA ;&mark2_a STA
#0000 ;&backtrack STA ;&findloop STA
;&MAIN_LOOP GOTO
( Else if mark2 is not active )
&2_3 ;&mark2_a LDA ;&2_4 JCN*
( If painter is at mark1 )
&2_3_1 ;&mark1_a LDA NOT ,&2_3_2 JCN
;&paint_x LDA* ;&mark1_x LDA* NEQ* ,&2_3_2 JCN
;&paint_y LDA* ;&mark1_y LDA* NEQ* ,&2_3_2 JCN
( If painter and mark1 are facing in the same direction )
&2_3_1_1 ;&paint_d LDA ;&mark1_d LDA NEQ ,&2_3_1_else
#00 ;&mark1_a STA
;&turn_around CALL
;&paint_here CALL
;&PAINT GOTO
&2_3_1_else
#0001 ;&backtrack STA ;&findloop STA
;&mark1_d LDA ;&paint_d STA
;&MAIN_LOOP GOTO
( If findloop is True )
&2_3_2 ;&findloop LDA NOT ,&2_3_end JCN
;&paint_x LDA* ;&mark2_x STA*
;&paint_y LDA* ;&mark2_y STA*
;&paint_d LDA ;&mark2_d STA
#01 ;&mark2_a STA
&2_3_end ;&MAIN_LOOP GOTO
( Else if painter is at mark1 )
&2_4 ;&mark1_a LDA NOT ,&2_5 JCN
;&paint_x LDA* ;&mark1_x LDA* NEQ* ,&2_5 JCN
;&paint_y LDA* ;&mark1_y LDA* NEQ* ,&2_5 JCN
;&mark2_x LDA* ;&paint_x STA*
;&mark2_y LDA* ;&paint_y STA*
;&mark2_d LDA ;&paint_d STA
#0000 ;&mark1_a STA ;&mark2_a STA
#00 ;&backtrack STA
;&turn_around CALL
;&paint_here CALL
;&PAINT GOTO
( Else if painter is at mark2 )
&2_5 ;&mark2_a LDA NOT ,&2_end JCN
;&paint_x LDA* ;&mark2_x LDA* NEQ* ,&2_end JCN
;&paint_y LDA* ;&mark2_y LDA* NEQ* ,&2_end JCN
;&paint_x LDA* ;&mark1_x STA*
;&paint_y LDA* ;&mark1_y STA*
;&mark2_d LDA ;&mark1_d STA
;&mark2_d LDA ;&paint_d STA
#0001 ;&mark1_a STA ;&mark2_a STA
&2_end ;&MAIN_LOOP GOTO
&check_3
;&wall_count LDA #03 NEQ ,&check_4 JCN
#00 ;&mark1_a STA
;&paint_here CALL
;&PAINT GOTO
&check_4
;&wall_count LDA #04 NEQ ;&MAIN_LOOP JCN*
;&paint_here CALL_RET
( Update the variable tracking the number of walls around the painter )
&update_neighbour_wall_count ( -- wall_count )
#04 ,&front? JSR ,&back? JSR ,&left? JSR ,&right? JSR
ADD* ADD SUB ;&wall_count STA RETURN
( Return 1 if the pixel in the direction is inside the fill region, otherwise 0 )
&front? #00 ,&dir_inside? JMP
&back? #02 ,&dir_inside? JMP
&left? #03 ,&dir_inside? JMP
&right? #01 ,&dir_inside? JMP
&front_left? #03 ,&diag_inside? JMP
&back_left? #02 ,&diag_inside? JMP
&dir_inside? ;&col_t LDA SWP ,&get_dir_coords JSR ;check_canvas_pixel_colour CALL_RET
&diag_inside? ;&col_t LDA SWP ,&get_diag_coords JSR ;check_canvas_pixel_colour CALL_RET
( 0:front 1:right 2:back 3:left )
&get_dir_coords ( relative_d -- x* y* )
;&paint_d LDA ADD MOD4 ( d )
TO_SHORT DUP* INC MOD4 SWP* ( d+1* d* )
DOUBLE* ;&_dir_offsets ADD* LDA* SWP* ( d+1* | y_offset* )
DOUBLE* ;&_dir_offsets ADD* LDA* SWP* ( x_offset* | y_offset* )
,&to_abs_coords JMP
( 0:front-right 1:back-right 2:back-left 3:back-left )
&get_diag_coords ( relative_d -- x* y* )
;&paint_d LDA ADD MOD4 ( d )
#0003 ROT SUB INCk* MOD4 ( 3-d* 4-d* )
DOUBLE* ;&_diag_offsets ADD* LDA* SWP* ( y_offset* 3-d* )
DOUBLE* ;&_diag_offsets ADD* LDA* SWP* ( x_offset* y_offset* )
,&to_abs_coords JMP
&to_abs_coords ( x_offset* y_offset* -- x* y* )
SWP* ;&paint_x LDA* ADD* SWP* ( x* y_offset* )
;&paint_y LDA* ADD* RETURN ( x* y* )
&_dir_offsets ffff 0000 0001 0000
&_diag_offsets ffff ffff 0001 0001
&paint_here ,&col_p LDR ,&paint_x LDR* ,&paint_y LDR* ;set_canvas_pixel_colour CALL_RET
&move_forward #00 ;&get_dir_coords CALL ,&paint_y STR* ,&paint_x STR* RETURN
&turn_left #ff ,&_turn JMP
&turn_right #01 ,&_turn JMP
&turn_around #02 ,&_turn JMP
&_turn ,&paint_d LDR ADD MOD4 ,&paint_d STR RETURN
&paint_x $2 &mark1_x $2 &mark2_x $2 ( x-coords )
&paint_y $2 &mark1_y $2 &mark2_y $2 ( y-coords )
&paint_d $2 &mark1_d $1 &mark2_d $1 ( directions, 0:up 1:right 2:down 3:left )
&wall_count $1 &mark1_a $1 &mark2_a $1 ( neighbouring walls count, and mark active bools )
&col_p $1 &col_t $1 ( paint colour and target colour )
&backtrack $1 &findloop $1
( ---------------------------------------------------------------------------- )
( T O O L B O X )
@delay ( coarse* -- )
#0000 SWP* SUB* #0000 ( coarse* fine* )
&loop_fine
INC* DUP* ORA ,&loop_fine JCN
&loop_coarse
SWP* INC* SWP* OVR* ORA ,&loop_fine JCN
POP* POP* RETURN
@print_newline LIT <LINEFEED> /CONSOLE.WRITE! RETURN
@print_space LIT <SPACE> /CONSOLE.WRITE! RETURN
@ -393,7 +612,7 @@
&end POP* RETURN
( Print a single byte to the console )
( Print a single byte to the console in decimal )
@print_byte_decimal_signed ( byte -- )
DUP* IS_POSITIVE ,print_byte_decimal JCN
LIT "- /CONSOLE.WRITE! NEGATE
@ -401,7 +620,7 @@
;convert_byte_to_decimal_string CALL
PRINTF(%s) RETURN
( Print a single short to the console )
( Print a single short to the console in decimal )
@print_short_decimal_signed ( short* -- )
DUP* IS_POSITIVE* ,print_short_decimal JCN
LIT "- /CONSOLE.WRITE! NEGATE*
@ -414,9 +633,11 @@
&loop LDAk DUP ,&print JCN POP POP* RETURN
&print /CONSOLE.WRITE! INC* ,&loop JMP
( Print a byte as TRUE or FALSE )
( Print a value as TRUE or FALSE )
@print_bool_short ( bool* -- )
TO_BOOL*
@print_bool ( bool -- )
;&true ROT ,&print JCN POP* ;&false &print PRINTF(%s)
;&true ROT ,&print JCN POP* ;&false &print PRINTF(%s) RETURN
&true "TRUE <NULL> &false "FALSE <NULL>
@ -602,10 +823,15 @@
;ui__clear_memory_region CALL
( Clear all registered mouse zones )
;ui__zones ;ui__zones/end #0000
;ui__clear_memory_region CALL_RET
;ui__clear_memory_region CALL
( Set mouse colour to 0 )
COL_T1 ;ui__set_cursor_colour CALL_RET
&pop_null_callback POP
&null_callback RETURN
@ui__set_cursor_colour ( sprite_colour -- )
;ui__redraw_cursor/cursor_colour STA RETURN
( Fill a region of memory with a given value. )
@ui__clear_memory_region ( start_addr* end_addr* value* -- )
STH* SWP* ( end* addr* | val* )
@ -663,7 +889,7 @@
COL_00 AND_FG /SCREEN.SPRITE!
( Draw the mouse cursor under the current mouse position )
/MOUSE.X? /SCREEN.X! /MOUSE.Y? /SCREEN.Y!
COL_T1 AND_FG /SCREEN.SPRITE!
[ LIT &cursor_colour $1 ] AND_FG /SCREEN.SPRITE!
RETURN
@ui__restart_mouse1_drag ( -- )
@ -999,6 +1225,7 @@
@canvas_screen
;ui__clear CALL
;colour_palette_canvas #04 ;ui__set_colour_palette CALL
#03 ;ui__set_cursor_colour CALL
;canvas_screen__draw CALL
;canvas_screen__tool ;ui__register_mouse1_callback CALL
;canvas_screen__B ;ui__register_B_callback CALL
@ -1008,7 +1235,7 @@
@canvas_screen__B NORETURN ;tool_palette_screen GOTO
@canvas_screen__tool
( Dispatch control to the routine of the active tool )
/V.ACTIVE_TOOL? MUL4* JMP
/V.ACTIVE_TOOL? MUL4 JMP
;&pan GOTO ;&zoom GOTO ;&brush_1 GOTO ;&brush_2 GOTO
;&brush_3 GOTO ;&line GOTO ;&rect GOTO ;&fill GOTO
@ -1045,16 +1272,17 @@
/MOUSE.X? /SCREEN.X! /MOUSE.Y? /SCREEN.Y! COL_1 /SCREEN.PIXEL!
RETURN
&brush_2 RETURN
&brush_3 RETURN
&line RETURN
&rect RETURN
&fill RETURN
&fill ( released? -- )
IF_FALSE RETURN #01
/MOUSE.X? /V.CANVAS.X? SUB*
/MOUSE.Y? /V.CANVAS.Y? SUB*
;fill_canvas_from_point CALL
;draw_canvas CALL
RETURN
( Render the canvas to the screen from the raw program data )
@draw_canvas ( -- )
@ -1580,11 +1808,10 @@ e060 6060 6060 f000 ( l ) 0000 6cfe d6d6 c600 ( m )
0008 0008 0008 0008 0000 0000 0000 0000 0020 0020 0020 0020
0008 0d00 0000 0000 0000 5500 0000 0000 0020 6000 0000 0000
@canvas_buffer
(
This label needs to be at the end of the
program, to ensure that there is enough space
to work with the largest possible image.
)
ffa1 adad a1bf 81ff ff81 f5d1 f781 c1ff ffff 0000 ffff 0000