class ZXLib::Gfx::Sprite8

Sprite drawing routines.

See also ZXLib::Gfx::Sprite8::Macros.

By default all drawing method routines are produced. To select only required routines define a ZXLib::Gfx::Sprite8::DRAW_METHODS constant before requiring this file.

module ZXLib
  module Gfx
    class Sprite8
      DRAW_METHODS = [:xor]
    end
  end
end
require 'zxlib/gfx/sprite8'

Constants

CALCULATE_SCREEN_ADDRESS

Configures the method in which draw_sprite8 calculates the screen address from pixel coordinates.

The routine is 60 bytes in size and can be set up in one of the following ways:

  • :inline

    The calculation is inline which is faster and backwards compatible but the calculation routine can't be re-used by Macros.gfx_sprite8_draw macro.

  • :subroutine

    The calculation is set up as a subroutine, which can be reached via draw_sprite8.calc_scr_addr label. See also Macros.gfx_sprite8_calculate_screen_address.

  • :external

    The calculation routine is NOT created. In this instance the only way to call draw_sprite8 is via the Macros.gfx_sprite8_draw macro.

CHECK_HEIGHT_SANITY

Controls if draw_sprite8 should check if the sprite pixel height is larger than “top lines to skip” parameter.

DRAW_METHODS

Array of supported drawing methods: :xor, :or, :set, :mask_or

DRAW_METHOD_MASK_OR
DRAW_METHOD_OR
DRAW_METHOD_SET
DRAW_METHOD_XOR
SCREEN_ADDRESS

A default screen address used by the routines. You may change this value by overriding label sprite8_screen_address when importing Sprite8 code.

Public Instance Methods

draw_sprite8() click to toggle source

Draws a sprite using one of the selected drawing methods with an arbitrary pixel height and width.

Pixel data for sprites must be laid column-wise:

1st 8-pixel column bytes
2nd 8-pixel column bytes
...

Example:

A sprite 16x2 pixels:

 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
░░░░████████░░░░██░░░░░░░░░░░░██ 0
░░██░░░░░░░░██░░████████████████ 1

db    0b00111100, # 1st column
      0b01000010,

      0b10000001, # 2nd column
      0b11111111

In an AND+OR (a.k.a. MASK_OR) mode bytes representing shape of a sprite must be intertwined with sprite's mask bytes:

1st bitmap byte
1st mask byte
2nd bitmap byte
2nd mask byte
...

This routine is optimised for vertical sprites with pixel height > width and square ones with width <= 24 pixels.

de

An address of sprite data to be drawn.

h

A screen vertical (y) coordinate of a sprite's top-leftmost pixel: [0, 191].

l

A screen horizontal (x) coordinate of a sprite's top-leftmost pixel: [0, 255].

b

How many pixel lines to skip from the sprite's top.

c

A width of a sprite in bytes (columns) ((pixel width + 7) / 8): [1, 32].

a'

A height of a sprite in pixel lines: [1, 192].

f'

Flags specifying a drawing method (see below).

To specify a negative vertical coordinate (y < 0) set h (or l see below) to 0 and b to an absolute value of y.

To specify a negative horizontal coordinate (x < 0):

  • de should point to the remaining visible sprite columns,

  • c should contain the number of visible sprite columns,

  • if x modulo 8 is between -1 and -7 then h should contain x modulo 8 as a twos complement negative number and l should contain a vertical coordinate (y) instead.

Drawing methods:

  mode:    OR    SET     XOR   AND+OR
    CF:     0      1       0        1
    ZF:     0      0       1        1
how to: ora 1    scf    cp a     cp a
               sbc a              scf
Uses

af, af', bc, de, hl, bc', de', hl', ix, stack: max 6 bytes, in AND+OR mode also iy.

# File lib/zxlib/gfx/sprite8.rb, line 364
ns :draw_sprite8 do
  unless CALCULATE_SCREEN_ADDRESS == :external
                push bc             # save width and skip
    case CALCULATE_SCREEN_ADDRESS
    when :subroutine
                call calc_scr_addr
    else
                gfx_sprite8_calculate_screen_address(scraddr:sprite8_screen_address)
    end
    skip_addr   push hl             # HL: screen addr
                                    # C: shift 0..14
  end
  addr_on_stack ld   hl, maskshift

  select((maskshift + 14) & 0xFF){|m| m < 14 }.then do |_|
                ld   b, 0
                add  hl, bc         # HL: -> (maskshift + shift)
  end.else do
                ld   a, c
                add  l
                ld   l, a           # HL: -> (maskshift + shift)
  end
                ld   a, [hl]        # A: rotate mask

                exx
                ld   e, a           # E: rotate mask
                pop  hl             # HL: screen addr
                exx                 # E': rotate mask, H'L': screen addr

                ex   af, af         # A: height + CZ
                ld   b, a           # B: height
                ld   a, c           # A: shift 0..14

  unless DRAW_METHOD_XOR or DRAW_METHOD_OR or DRAW_METHOD_SET or DRAW_METHOD_MASK_OR
    raise Syntax, "there must be at least one draw method selected"
  end

  if DRAW_METHOD_XOR or DRAW_METHOD_OR
    if DRAW_METHOD_SET or DRAW_METHOD_MASK_OR
                jr   C, aocskip
    end
    if DRAW_METHOD_XOR and DRAW_METHOD_OR
                jr   NZ, orskip
    end

    if DRAW_METHOD_XOR
                add  a
                jr   Z, fastxor
                ld   hl, sprxor.start
                push hl             # jump addr
                ld   hl, jumpxor - 2
                jp   skipall
      fastxor   ld   hl, sprxor.fstcopy
                jp   skpfast
    end

    if DRAW_METHOD_OR
      orskip    add  a
                jr   Z, fastor
                ld   hl, spror.start
                push hl             # jump addr
                ld   hl, jumpor - 2
                jp   skipall
      fastor    ld   hl, spror.fstcopy
                jp   skpfast
    end
  end

  if DRAW_METHOD_SET and DRAW_METHOD_MASK_OR
    aocskip     jr   Z, andskip
  elsif DRAW_METHOD_SET or DRAW_METHOD_MASK_OR
    aocskip     label
  end

  if DRAW_METHOD_SET
                add  a
                jr   Z, fastclr
                ld   hl, spclror.start
                push hl             # jump addr
                ld   hl, jumpclror - 2
                jp   skipall
    fastclr     ld   hl, spclror.fstcopy
                jp   skpfast
  end

  if DRAW_METHOD_MASK_OR
    andskip     ld   hl, spandor.start
                add  a
                jr   Z, fastaor
                push hl             # jump addr
                ld   hl, jumpandor - 4
                add  a
                adda_to h, l
                ld   a, [hl]
                ld   iyl, a
                inc  hl
                ld   a, [hl]
                ld   iyh, a
                inc  hl
                jp   skipal2
    fastaor     ld   hl, spandor.fstcopy
                jp   skpfast
  end

  if DRAW_METHOD_XOR or DRAW_METHOD_OR or DRAW_METHOD_SET
    skipall     adda_to h, l
  end
  skipal2       ld   a, [hl]
                ld   ixl, a
                inc  hl
                ld   a, [hl]
                ld   ixh, a
                pop  hl             # jump addr
  skpfast       ld   a, b           # height
                pop  bc             # skip + width
                sub  b              # height - skip top lines
  if CHECK_HEIGHT_SANITY
                ret  C              # return if skip top > height
                ret  Z              # return if skip top == height
  end
                jp   (hl)

  if CALCULATE_SCREEN_ADDRESS == :subroutine
    calc_scr_addr gfx_sprite8_calculate_screen_address(scraddr:sprite8_screen_address, subroutine:true)
  end
end