Sprite Masking

bbc micro/electron/atom/risc os coding queries and routines
Post Reply
jregel
Posts: 218
Joined: Fri Dec 20, 2013 6:39 pm
Location: Gloucestershire
Contact:

Sprite Masking

Post by jregel » Fri Jun 19, 2020 8:02 pm

I've spent the afternoon (off work!) trying to get my head around sprite masks and would appreciate some guidance.

So I'm wanting to plot a sprite onto a background, but I don't want an ugly box around sprite, so the answer is to use a mask, right?

The parts of the sprite I don't want to display are black (colour 0) and so conceptually, I guess I want to write a pixel where the value <> 0, but then the BBC display format isn't straightforward, so assume I need to do something like read a byte, do some logical operation on a number of bits (actual number determined by screen mode) to get see if a pixel is transparent, then somehow merge it with the background?

But then I've seen other references to using a separate sprite mask (Beebspriter allows using a transparent colour or separate mask, but doesn't have documentation (I've seen) that demonstrates its use)

I did have a look at some sample code, but it hasn't sunk in and I'm still at the concept stage.

Any ideas/hints/guidance appreciated! Thanks.
BBC Master Turbo, Retroclinic External Datacentre, VideoNuLA, PiTubeDirect with Pi Zero, Gotek USB Floppy Emulator

User avatar
Andrew_Waite
Posts: 238
Joined: Tue Aug 30, 2016 3:58 pm
Contact:

Re: Sprite Masking

Post by Andrew_Waite » Fri Jun 19, 2020 8:24 pm

Planet Nubium Two uses a sprite mask to display the Rocketman sprite.

First, the background where the sprite is to be plotted is copied to a scratch area of memory. The screen memory then undergoes a bitwise AND with the sprite mask, then undergoes a bitwise OR with the sprite itself. The result is then stored in screen memory.

Deleting the sprite involves copying the original background stored in the scratch area of memory over the top of the sprite.

There is another explanation of this process here : https://en.wikipedia.org/wiki/Mask_(com ... mage_masks

The sprites that animate Rocketman's run cycle, and the sprite mask for each part of the run cycle, are shown in the picture below.
Attachments
sprite a.png
Last edited by Andrew_Waite on Sat Jun 20, 2020 1:12 am, edited 1 time in total.

jregel
Posts: 218
Joined: Fri Dec 20, 2013 6:39 pm
Location: Gloucestershire
Contact:

Re: Sprite Masking

Post by jregel » Fri Jun 19, 2020 10:12 pm

Thanks Andrew, that was really helpful and it makes sense.

I've done a bit of playing with the approach you suggested and it's working!

You say that's how you did it for Planet Nubium Two. What did you do for the first game?
BBC Master Turbo, Retroclinic External Datacentre, VideoNuLA, PiTubeDirect with Pi Zero, Gotek USB Floppy Emulator

User avatar
Andrew_Waite
Posts: 238
Joined: Tue Aug 30, 2016 3:58 pm
Contact:

Re: Sprite Masking

Post by Andrew_Waite » Sat Jun 20, 2020 12:58 am

The Rocketman sprite in the first Planet Nubium game was drawn using the XOR method. Here the background undergoes a bitwise XOR with the sprite. The method is quite simple and the same bitwise XOR routine is used to remove the sprite. However, with this method when Rocketman crosses over a coloured tile the colours of the sprite are corrupted.

The patrol lines of the guardians (the baddies) in both games never cross over a tile, so the guardian sprites are simply written to the screen and deleted by drawing a black rectangle over the top of them.

There is some more material on sprite logic here :

http://tibasicdev.wikidot.com/68k:sprites

Naomasa298
Posts: 391
Joined: Sat Feb 16, 2013 12:49 pm
Contact:

Re: Sprite Masking

Post by Naomasa298 » Sat Jun 20, 2020 1:30 am

I was originally intending to use the method you describe for my own masked sprites, but I have to figure out how to combine that with my compression routines. I would need the sprite mask to reflect the compressed data, which I think I can do but it will end up making my plot routines relatively slow, given the size of the sprites in question.

Shouldn't be a huge problem since it's not a fast moving game, but I'll have to see.

User avatar
Snuggsy187
Posts: 139
Joined: Wed Apr 03, 2019 9:53 pm
Contact:

Re: Sprite Masking

Post by Snuggsy187 » Mon Jun 22, 2020 9:21 am

Hi jregel.

Some games use a 256 byte mask lookup table (like BBC Frak - stored at &0A00), this may save on creating and storing all those masks, but will cost extra cycles. This assumes you're not using logical colour 0 as part of your sprite !

Mask Table.txt
(1.09 KiB) Downloaded 37 times
PUSH PARCHMENT > POKE LOCK > PULL PARCHMENT

User avatar
tricky
Posts: 4687
Joined: Tue Jun 21, 2011 9:25 am
Contact:

Re: Sprite Masking

Post by tricky » Mon Jun 22, 2020 6:26 pm

RichTW and I compare masked sprite routines with a table here: http://www.retrosoftware.co.uk/forum/vi ... f=73&t=932

User avatar
Snuggsy187
Posts: 139
Joined: Wed Apr 03, 2019 9:53 pm
Contact:

Re: Sprite Masking

Post by Snuggsy187 » Mon Jun 22, 2020 7:52 pm

Hi Tricky,
Very interesting, many thanks for this (and RichTW) ! :D
Never thought of self-modding the code regarding the mask table !

Cheers :D
PUSH PARCHMENT > POKE LOCK > PULL PARCHMENT

AJW
Posts: 907
Joined: Sun Feb 15, 2004 2:01 pm
Contact:

Re: Sprite Masking

Post by AJW » Wed Jul 08, 2020 8:12 pm

Andrew_Waite wrote:
Sat Jun 20, 2020 12:58 am
The Rocketman sprite in the first Planet Nubium game was drawn using the XOR method. Here the background undergoes a bitwise XOR with the sprite. The method is quite simple and the same bitwise XOR routine is used to remove the sprite. However, with this method when Rocketman crosses over a coloured tile the colours of the sprite are corrupted.

The patrol lines of the guardians (the baddies) in both games never cross over a tile, so the guardian sprites are simply written to the screen and deleted by drawing a black rectangle over the top of them.

There is some more material on sprite logic here :

http://tibasicdev.wikidot.com/68k:sprites
I have a tiled background and a second layer of sprites - players and a cursof- over it. I want to move the layer 2 objects especially the cursor without havinv to redraw the whole tiled map which really slows the cursor down. What is the best option to remove the layer 2 sprite 'smudge'?

AJW
Posts: 907
Joined: Sun Feb 15, 2004 2:01 pm
Contact:

Re: Sprite Masking

Post by AJW » Wed Jul 08, 2020 8:12 pm

AJW wrote:
Wed Jul 08, 2020 8:12 pm
Andrew_Waite wrote:
Sat Jun 20, 2020 12:58 am
The Rocketman sprite in the first Planet Nubium game was drawn using the XOR method. Here the background undergoes a bitwise XOR with the sprite. The method is quite simple and the same bitwise XOR routine is used to remove the sprite. However, with this method when Rocketman crosses over a coloured tile the colours of the sprite are corrupted.

The patrol lines of the guardians (the baddies) in both games never cross over a tile, so the guardian sprites are simply written to the screen and deleted by drawing a black rectangle over the top of them.

There is some more material on sprite logic here :

http://tibasicdev.wikidot.com/68k:sprites
I have a tiled background and a second layer of sprites - players and a cursor- over it. I want to move the layer 2 objects especially the cursor without having to redraw the whole tiled map which really slows the cursor down. What is the best option to remove the layer 2 sprite 'smudge'?

RobC
Posts: 3000
Joined: Sat Sep 01, 2007 10:41 pm
Contact:

Re: Sprite Masking

Post by RobC » Wed Jul 08, 2020 9:40 pm

You can save the background behind each sprite and then restore it when the sprites move.

Alternatively, if you're using mode 2, you can allocate 4 colours for the background and 3 (+invisible) for the sprites. You then setup the palette so that the XOR method causes the correct sprite or background colour to be shown. Games like Firetrack use this method.

julie_m
Posts: 235
Joined: Wed Jul 24, 2019 9:53 pm
Location: Derby, UK
Contact:

Re: Sprite Masking

Post by julie_m » Wed Jul 08, 2020 9:41 pm

Hmmmm, I think we might be on the same page here!

I'm currently working on some code to draw sprites on screen, using a dodgy-sounding (but very fast! and in the grand scheme of things, harmless) technique: self-modifying code! It isn't quite ready to post yet, but as soon as it is working as I want, I'll put it here.

If you fancy having a go yourself in the meantime, the rough sketch is to have subroutines to:
  • Calculate the screen address for a given X,Y position
  • Copy from an area of screen directly to some block of memory; collapsing the weird on-screen order into a flat list
  • Copy from some block of memory to the screen, reordering the bytes as we go
  • The sequence LDA(scrn),Y ; STA oldbg,X ; AND mask,X ; EOR sprite,X ; STA(scrn),Y
The last operation saves a copy of what was on the screen, ANDs it with the mask, EORs the result (which should have 0s in the sprite wherever there are 1s in the mask; the mask will have 0s wherever the sprite is to show through) with the sprite data and stores it into screen memory.

I'm planning to do everything with just whole-byte resolution (4 pixels in MODE 5) and use multiple animation frames to position a sprite horizontally within its coarse box.

The linearisation will be accomplished by reading from (scrn),Y and writing to mem,X (the operand in this instruction will be written to directly before the subroutine is called, to tell it where to stick the data. We will have to be a bit careful about this). X can simply increase monotonically; we jump out of the loop as soon as it becomes equal to the length of the sprite data, so we never unnecessarily update the screen position. Y and the contents of (scrn) are manipulated in cunning ways to move around the screen. At the beginning of each scanline, we save a copy of Y; then after copying each byte, we work along the scanline by adding 8 to Y. But if this would take us past the sprite width, we instead move onto the next scanline, by increasing the Y value saved from the beginning of the scanline. If this takes us to the next character row, we need to add &140 (in MODE 4 and MODE 5; or &280 in MODE 1 and MODE 2) to the screen address and reset Y to 0; then we can begin the next scanline.

AJW
Posts: 907
Joined: Sun Feb 15, 2004 2:01 pm
Contact:

Re: Sprite Masking

Post by AJW » Wed Jul 08, 2020 10:28 pm

RobC wrote:
Wed Jul 08, 2020 9:40 pm
You can save the background behind each sprite and then restore it when the sprites move.
specifically how is this done? If the sprite moves say left a byte in Mode 1 then how do I know to previously save the block of bytes to the right of it?

Alternatively, if you're using mode 2, you can allocate 4 colours for the background and 3 (+invisible) for the sprites. You then setup the palette so that the XOR method causes the correct sprite or background colour to be shown. Games like Firetrack use this method.
Will need to look into the xor method as once I understood it but no longer do.

Naomasa298
Posts: 391
Joined: Sat Feb 16, 2013 12:49 pm
Contact:

Re: Sprite Masking

Post by Naomasa298 » Wed Jul 08, 2020 11:33 pm

AJW wrote:
Wed Jul 08, 2020 10:28 pm
specifically how is this done? If the sprite moves say left a byte in Mode 1 then how do I know to previously save the block of bytes to the right of it?
When the sprite moves, you know which direction it's is going to move, so you know where it's going to end up. Save the block of bytes that represents the background there.
Will need to look into the xor method as once I understood it but no longer do.
You just EOR the background with the sprite. If the background is black, the sprite won't change. Then you EOR it again to remove it (since A EOR X EOR X = A).

julie_m
Posts: 235
Joined: Wed Jul 24, 2019 9:53 pm
Location: Derby, UK
Contact:

Re: Sprite Masking

Post by julie_m » Thu Jul 09, 2020 1:02 am

There is an even fancier trick you can use. That is to EOR a sprite with a shifted version of itself. Then you can EOR this against what is already on the screen, to remove the sprite from its old position and redraw in in its new position in a single operation. Depending how many animation frames you have, it may not be feasible to cover every possible transition, though.

AJW
Posts: 907
Joined: Sun Feb 15, 2004 2:01 pm
Contact:

Re: Sprite Masking

Post by AJW » Thu Jul 09, 2020 9:03 am

So move , save new, plot, move, restore old, save new

RobC
Posts: 3000
Joined: Sat Sep 01, 2007 10:41 pm
Contact:

Re: Sprite Masking

Post by RobC » Thu Jul 09, 2020 11:21 am

AJW wrote:
Wed Jul 08, 2020 10:28 pm
Will need to look into the xor method as once I understood it but no longer do.
In mode 2, you have 16 logical colours. If you partition these into 4 background colours and 3 foreground/sprite colours (+mask), you can define the palette in such a way that the EOR effects are largely mitigated.

So, if we call our background colours A, B, C and D and our foreground colours P, Q and R, we would define the palette as follows:

0 -> A
1 -> P
2 -> Q
3 -> R
4 -> B
5 -> P
6 -> Q
7-> R
8 -> C
9 -> P
10-> Q
11-> R
12-> D
13-> P
14-> Q
15 -> R

Now, tile/backgrounds should be drawn in colours 0, 4, 8 and 12 which are mapped to our chosen colours A, B, C and D. Sprites should be drawn in colours 0 (mask), 1, 2 and 3. When the sprites are EORed with the background tiles, their colours are mapped to our chosen colours P, Q and R irrespective of the colour of the tile their are being plotted on top of. And as has been said, we can use EOR to unplot and magically restore the background without having to replot it.

Things still get messy if two sprites are plotted on top of each other but that tends to indicate a collision in a game and can usually be avoided.

You can use the same trick to give 8 colour backgrounds and single colour sprites in mode 2 or have 2 colour backgrounds and single colour sprites in modes 1 & 5.

User avatar
tricky
Posts: 4687
Joined: Tue Jun 21, 2011 9:25 am
Contact:

Re: Sprite Masking

Post by tricky » Thu Jul 09, 2020 2:56 pm

I once started a Mr Do's castle game and used 1,3,2 colours with single colour foreground, two colour background and drew the sprites in between.

In Sarah's talk about White Light, she used a similar technique, but payed a couple of extra cycles to store the sprites and background data in the same memory and then AND # to clear the sprites - really clever and on a beeb, a great compromise.

http://abug.org.uk/index.php/2020/06/06 ... ah-walker/

User avatar
Andrew_Waite
Posts: 238
Joined: Tue Aug 30, 2016 3:58 pm
Contact:

Re: Sprite Masking

Post by Andrew_Waite » Thu Jul 09, 2020 3:17 pm

I have a tiled background and a second layer of sprites - players and a cursof- over it. I want to move the layer 2 objects especially the cursor without havinv to redraw the whole tiled map which really slows the cursor down. What is the best option to remove the layer 2 sprite 'smudge'?
Rather than redrawing the whole background tile layer, only store the part of the tile layer that the sprite frame will be written over to the scratch area of memory, then copy this scratch area of memory back to screen memory to delete the sprite.

Alternatively, if you have a spare bank of Sideways RAM available, then a copy of the entire background tile layer could be stored in SWRAM. To delete the sprite the part of the tile layer overwritten by the sprite can be copied from the SWRAM bank to screen memory. This is profligate with memory, but removes the need to store the part of the tile layer that the sprite frame will be written to in the scratch area of memory.

AJW
Posts: 907
Joined: Sun Feb 15, 2004 2:01 pm
Contact:

Re: Sprite Masking

Post by AJW » Thu Jul 09, 2020 7:27 pm

Think I've been overthinking; as you say restore area before copying new area and plotting sprite.

julie_m
Posts: 235
Joined: Wed Jul 24, 2019 9:53 pm
Location: Derby, UK
Contact:

Re: Sprite Masking

Post by julie_m » Thu Jul 09, 2020 9:15 pm

Here's my effort so far!

I have working subroutines to calculate the start of screen memory given X and Y co-ordinates; plot a sprite directly onto the screen; and grab data from screen to use as a sprite. Here I'm storing the location of data in a zero page pointer as though to use with a (zp),Y instruction; this is an artefact left over form an earlier version before I did my most aggressive optimisaion.

This is written in BeebAsm (my homebrew universal (Debian / Ubuntu / Mint / even Raspbian) .deb package with incorrect extension, but $ sudo dpkg -i beebasm_1.0-9_all.xls will install it just fine. Or get it from GitHub here), but it should not be too awkward to port back to BBC BASIC:

Code: Select all

ORG &5000

ac16 = &70  \  a 16-bit temporary "accumulator"
scrn = &72  \  base address on screen
data = &74  \  base address of data
scY  = &76  \  Y reg as pointer into screen
spW  = &7C  \  (sprite width in bytes - 1) * 8
daL  = &7D  \  data length

.begin

\  X => X position in byte size cells
\  Y => Y position in scanlines
\  sets scrn to base address
\  leaves scanline within char row in Y

.calc_start
   JMP real_calc_start

\  Y => initial scanline within char row
\  scrn  => base address on screen
\  data  => base address of sprite data

.plot_sprite
    JMP real_plot_sprite

\  Y => initial scanline within char row
\  scrn  => base address on screen
\  data  => base address of sprite data

.grab_sprite
    JMP real_grab_sprite

.real_calc_start
    TXA  \  save X register on stack
    PHA
    TYA
    \  drop lowest 3 bits of scanline
    LSR A
    LSR A
    LSR A
    \  get start address of character row
    TAX
    LDA char_rows_L,X
    STA scrn
    LDA char_rows_H,X
    STA scrn+1
    PLA  \ retrieve X
    STA ac16
    LDA #0
    STA ac16+1

    JSR asl_ac16
    JSR asl_ac16
    JSR asl_ac16
    \  we know C=0 here
    LDA ac16
    ADC scrn
    STA scrn
    LDA ac16+1
    ADC scrn+1
    STA scrn+1
    TYA
    AND #&07
    TAY
    RTS

    \  16-bit ASL on temporary value
    .asl_ac16
    ASL ac16
    ROL ac16+1
    RTS

.real_plot_sprite
    LDA data            \  copy data pointer right into
    STA plot_scanline+1 \  an instruction in RAM
    LDA data+1          \  this is ugly but quick
    STA plot_scanline+2
    STY scY     \  save Y as used for screen pointer
    LDX #0      \  X is index in data

\  X increases linearly as we draw the sprite.
\  Y jumps as we progress along a scanline.
\  Y is initially scanline within character row. This code works
\  on a scanline-by-scanline basis and cleanly handles crossing
\  character row boundaries.

\  Watch the carry flag!  We have to be careful not to set it
\  accidentally, so as to avoid having to clear it deliberately

.plot_scanline
    LDA &5800,X
    INX
    STA (scrn),Y
    CPX daL     \  see if we reached the end of the data
    BCS plot_done

    \  don't wasting time updating a screen position where
    \  we are never going to plot anything anyway

    CPY spW
    BCS plot_next_scanline

    \  if we get here at all, it means we really have to
    \  move right along the scanline
    
    TYA         \  nothing has set C in the meantime
    ADC #8      \  this will leave C clear
    TAY
    BCC plot_scanline

.plot_next_scanline

    \  Watch the carry!  Either we branched here with BCS; or
    \  we fell through when BCC *did not* branch.

    LDY scY     \  back to beginning of scanline
    INY
    STY scY     \  move to next scanline
    TYA
    AND #7      \  see if we hit bottom of char row
    BNE plot_scanline

    \  If we get here, it means we reached the last scanline
    \  of our character row.  We now need to increase the
    \  screen address by &140 to reach the beginning of the
    \  first scanline in the next character row.

    TAY         \  A==0; reset Y for next scanline
    STA scY     \  save Y
    LDA scrn
    ADC #&3F
    STA scrn
    LDA scrn+1
    ADC #1      \  C will be clear when we get here
    STA scrn+1
    BCC plot_scanline

.plot_done
.rts
    RTS

.real_grab_sprite
    LDA data            \  copy data pointer right into
    STA grab_byte+1     \  an instruction in RAM
    LDA data+1          \  this is ugly but quick
    STA grab_byte+2
    STY scY     \  save Y as used for screen pointer
    LDX #0      \  X is index in data

.grab_scanline
    LDA (scrn),Y
.grab_byte
    STA &5800,X
    INX
    CPX daL     \  see if we reached the end of the data
    BCS grab_done

    CPY spW
    BCS grab_next_scanline
    
    TYA         \  nothing has set C in the meantime
    ADC #8      \  this will leave C clear
    TAY
    BCC grab_scanline

.grab_next_scanline
    LDY scY     \  back to beginning of scanline
    INY
    STY scY     \  move to next scanline
    TYA
    AND #7      \  see if we hit bottom of char row
    BNE grab_scanline

    TAY         \  A==0; reset Y for next scanline
    STA scY     \  save Y
    \ CLC         \  We got here because a BCS branched
    LDA scrn
    ADC #&3F
    STA scrn
    LDA scrn+1
    ADC #1      \  C will be clear when we get here
    STA scrn+1
    BCC grab_scanline

.grab_done
    RTS

.char_rows_L
    EQUB&0
    EQUB&40
    EQUB&80
    EQUB&C0
    EQUB&0
    EQUB&40
    EQUB&80
    EQUB&C0
    EQUB&0
    EQUB&40
    EQUB&80
    EQUB&C0
    EQUB&0
    EQUB&40
    EQUB&80
    EQUB&C0
    EQUB&0
    EQUB&40
    EQUB&80
    EQUB&C0
    EQUB&0
    EQUB&40
    EQUB&80
    EQUB&C0
    EQUB&0
    EQUB&40
    EQUB&80
    EQUB&C0
    EQUB&0
    EQUB&40
    EQUB&80
    EQUB&C0
.char_rows_H
    EQUB&58
    EQUB&59
    EQUB&5A
    EQUB&5B
    EQUB&5D
    EQUB&5E
    EQUB&5F
    EQUB&60
    EQUB&62
    EQUB&63
    EQUB&64
    EQUB&65
    EQUB&67
    EQUB&68
    EQUB&69
    EQUB&6A
    EQUB&6C
    EQUB&6D
    EQUB&6E
    EQUB&6F
    EQUB&71
    EQUB&72
    EQUB&73
    EQUB&74
    EQUB&76
    EQUB&77
    EQUB&78
    EQUB&79
    EQUB&7B
    EQUB&7C
    EQUB&7D
    EQUB&7E

ALIGN &100

.sprite_data
    EQUB&FF
    EQUB&81
    EQUB&42
    EQUB&24
    EQUB&18
    EQUB&AA
    EQUB&55
    EQUB&FF

.sprite_2W
    EQUB&0F:EQUB&F0     \ 00001111 11110000
    EQUB&30:EQUB&0C     \ 00110000 00001100
    EQUB&47:EQUB&E2     \ 01000111 11100010
    EQUB&88:EQUB&11     \ 10001000 00010001
    EQUB&88:EQUB&11     \ 10001000 00010001
    EQUB&47:EQUB&E2     \ 01000111 11100010
    EQUB&30:EQUB&0C     \ 00110000 00001100
    EQUB&0F:EQUB&F0     \ 00001111 11110000

.sprite_2H
    EQUB &FF            \ 11111111
    EQUB &81            \ 10000001
    EQUB &C1            \ 11000001
    EQUB &A1            \ 10100001
    EQUB &91            \ 10010001
    EQUB &89            \ 10001001
    EQUB &85            \ 10000101
    EQUB &83            \ 10000011

    EQUB &81            \ 10000001
    EQUB &81            \ 10000001
    EQUB &81            \ 10000001
    EQUB &81            \ 10000001
    EQUB &81            \ 10000001
    EQUB &81            \ 10000001
    EQUB &81            \ 10000001
    EQUB &FF            \ 11111111

.end
    \  NO MORE CODE
    
\  *SAVE the code

SAVE "SPR04",begin,end
It uses the following addresses:

scrn = &72-73 = address of sprite on screen
data = &74-75 = address of sprite data in memory (this gets written into the instructions in the actual subroutines)
spW = &7C = sprite width. This is actually the width in bytes (one byte of screen memory represents 8 pixels in MODE 4; 4 pixels in MODE 5 and MODE 1; 2 pixels in MODE 2) minus one, times eight; that is to say, the offset on screen from the first byte to the last byte, taking into account that groups of eight successive bytes represent the same horizontal position along eight successive scanlines, and a new 8-scanline-high block begins on the ninth byte.
daL = &7D = data length. This is the length of the "flattened" data in the sprite.

For the included samples, ?&7C and ?&7D should be 0, 8; 8, 16; and 0, 16 respectively.

After setting &74, &75, &7C and &7D appropriately, call calc_start with the desired co-ordinates in the X and Y registers (X is measured in byte-sized amounts, Y is measured in scanlines). This will leave just the three least significant bits in the Y register Then you can call plot_sprite to plot the sprite onto the screen. grab_sprite does the opposite, storing a block of data from the screen into memory as plot_sprite would expect it. This is for grabbing sprite data from a saved screen image generated using some other program.

You could easily make a version using EOR, either by copying the code again and making appropriate altertions; or by changing plot_scanline+4 to &51, thus changing the instruction following the INX to EOR (scrn),Y.

Note that you will need to change the offset between successive character rows if running in MODE 1 or MODE 2. &140 is correct for MODE 5 and MODE 4, but we actually add &13F. We know for sure the carry must be set, because we arrived at this point in the program with a BCS instruction. We could either waste two ticks of the clock clearing it, or just add one smaller than we originally thought. The length of a character row in the 20K modes is &280 bytes, so you will need to change the value to &27F.


I'm still experimenting with generating graphics to use for sprites, but probably will add saving the previous screen contents, ANDing with the mask and EORing with the sprite data next. (The old plot_sprite code probably then will become restore_bg ..... shame to waste it ;) ). Once I've got some sprites I'm properly happy with, and they move the way I want against a plain background, then I can try making masks for them .....

Naomasa298
Posts: 391
Joined: Sat Feb 16, 2013 12:49 pm
Contact:

Re: Sprite Masking

Post by Naomasa298 » Thu Jul 09, 2020 10:45 pm

julie_m wrote:
Thu Jul 09, 2020 9:15 pm
Here's my effort so far!
My equivalents can be found in the RPG project thread, along with all the optimisations everyone helped me with. No masking in those particular routines, but it's fairly easy to add an AND with mask then OR with data to plot. They do include RLE compression though.

RobC
Posts: 3000
Joined: Sat Sep 01, 2007 10:41 pm
Contact:

Re: Sprite Masking

Post by RobC » Fri Jul 10, 2020 7:51 am

tricky wrote:
Thu Jul 09, 2020 2:56 pm
In Sarah's talk about White Light, she used a similar technique, but payed a couple of extra cycles to store the sprites and background data in the same memory and then AND # to clear the sprites - really clever and on a beeb, a great compromise.

http://abug.org.uk/index.php/2020/06/06 ... ah-walker/
Thanks for the link - I hadn't seen Sarah's talk. That is a really clever way to save memory when using colour layers.

AJW
Posts: 907
Joined: Sun Feb 15, 2004 2:01 pm
Contact:

Re: Sprite Masking

Post by AJW » Fri Jul 10, 2020 8:03 pm

RobC wrote:
Thu Jul 09, 2020 11:21 am
AJW wrote:
Wed Jul 08, 2020 10:28 pm
Will need to look into the xor method as once I understood it but no longer do.
In mode 2, you have 16 logical colours. If you partition these into 4 background colours and 3 foreground/sprite colours (+mask), you can define the palette in such a way that the EOR effects are largely mitigated.

So, if we call our background colours A, B, C and D and our foreground colours P, Q and R, we would define the palette as follows:

0 -> A
1 -> P
2 -> Q
3 -> R
4 -> B
5 -> P
6 -> Q
7-> R
8 -> C
9 -> P
10-> Q
11-> R
12-> D
13-> P
14-> Q
15 -> R

Now, tile/backgrounds should be drawn in colours 0, 4, 8 and 12 which are mapped to our chosen colours A, B, C and D. Sprites should be drawn in colours 0 (mask), 1, 2 and 3. When the sprites are EORed with the background tiles, their colours are mapped to our chosen colours P, Q and R irrespective of the colour of the tile their are being plotted on top of. And as has been said, we can use EOR to unplot and magically restore the background without having to replot it.

Things still get messy if two sprites are plotted on top of each other but that tends to indicate a collision in a game and can usually be avoided.

You can use the same trick to give 8 colour backgrounds and single colour sprites in mode 2 or have 2 colour backgrounds and single colour sprites in modes 1 & 5.
I remember now although never did anything as clever as that, the essence of it, plotting over again to erase before moving and replotting. How is the background restored when you don't know what colour it's going to be?

julie_m
Posts: 235
Joined: Wed Jul 24, 2019 9:53 pm
Location: Derby, UK
Contact:

Re: Sprite Masking

Post by julie_m » Fri Jul 10, 2020 9:48 pm

AJW wrote:
Fri Jul 10, 2020 8:03 pm
I remember now although never did anything as clever as that, the essence of it, plotting over again to erase before moving and replotting. How is the background restored when you don't know what colour it's going to be?
Because it does not matter what colour the background was!

The truth table for the EOR function is as follows:

Code: Select all

..A..|..B..|.OUT.
-----+-----+-----
..0..|..0..|..0..
..0..|..1..|..1..
..1..|..0..|..1..
..1..|..1..|..0..
When you are EORing multi-bit numbers, you simply work on each corresponding pair of input bits separately (there is no interaction between one bit and any other) to get the equivalent bit of the output. So if we have for instance 0111 EOR 0100, the output will be 0011. The eights bit is zero in A and B therefore zero in the output. The fours bit is one in A and one in B, so zero in the output. The twos bit is one in A and zero in B, so one in the output; and the units bit is one in A and zero in B, so one in the output.

The point about this is, the truth table is symmetrical, and there is no way for one input to force the output always to a known state. If A is 0, the output is the same as B; if A is 1, the output is the opposite of B. And this means that (A EOR B) EOR B will always be the same as A.
If B = 0, then (A EOR B) EOR B will be the same as A EOR B; and (A EOR B) will be the same as A.
If B = 1, then (A EOR B) EOR B will be the opposite of A EOR B; and A EOR B will be the opposite of that = NOT (NOT A) = A.

So when you plot your sprite to the screen using EOR, if B is the background colour and F is the foreground colour, then your sprite will appear in colour B EOR F. And if you plot it to the screen again in the same place, you will be left with colour (B EOR F) EOR F = B.

In a 2-colour mode, this means that where a pixel is on in both the sprite and the corresponding position in the background, the result will be off. This can produce some strange effects.

In MODE 5 / MODE 1, you can use colours 0 and 1 for the background image and colours 0 and 2 for the sprites. Now B=1 and F=2, so B EOR F = 3, so where a pixel is on in both the sprite and the background, it will show colour 3. If you redefine the palette so colours 1, 2 and 3 are the same, then foreground and background will never clash. The downside is that you have halved the colour space; because what you have effectively done is, made a mask the size of the whole screen, using half the frame buffer memory (but splitting it between the bits in each byte, rather than into two blocks of bytes). You can use either a 4+4 or 8+2 colour split in MODE 2, depending how you assign bit values to the foreground and background.

julie_m
Posts: 235
Joined: Wed Jul 24, 2019 9:53 pm
Location: Derby, UK
Contact:

Re: Sprite Masking

Post by julie_m » Sat Jul 11, 2020 7:06 pm

BUG ALERT:
The loop-terminating test after the INX should be BEQ, otherwise it will not work with 256-byte-long sprites. :oops:

AJW
Posts: 907
Joined: Sun Feb 15, 2004 2:01 pm
Contact:

Re: Sprite Masking

Post by AJW » Mon Jul 13, 2020 8:22 pm

I get it redefine palette so that the EOR'd colour to produce the same colour no matter what background colour.

Post Reply

Return to “programming”