module ZXLib::Gfx::Clip::Macros
ZXLib::Gfx::Clip Macros for clipping lines to viewport rectangles.¶ ↑
Public Instance Methods
Creates a routine that calculates dx and dy. Used by: Macros.gfx_clip_line.
- NOTE
-
Swaps
bc,de,hlregisters with their alternative counterparts (EXX).
The routine expects (before EXX):
-
x0inxx', -
y0inyy', -
x1inxx, -
y1inyy.
The routine returns (after EXX):
h = abs(x1 - x0) / k l = abs(y1 - y0) / k a' = sgn(x1 - x0) XOR sgn(y1 - y0)
Finds such k that h and l does not overflow 8 bits.
sgn is a boolean: 0x00 for positive and 0xFF for negative.
Modifies: af, af', hl and 2 levels of the stack, swaps registers: bc, de, hl.
# File lib/zxlib/gfx/clip.rb, line 160 def gfx_clip_calculate_8bit_dx_dy_exx(xx=bc, yy=de, full_range_delta:true) raise ArgumentError unless [bc,de].include?(xx) and [bc,de].include?(yy) and xx != yy isolate do push yy # y1 push xx # x1 exx pop hl # x1 xor a # a: 0, CF: 0 sbc hl, xx # hl: dx = x1 - x0 if full_range_delta jp PO, dx_fits_range jp M, dx_positive jr dx_negative dx_fits_range label end jp P, dx_positive dx_negative cpl # a: -sgn ex af, af neg16 h, l # dx = -dx ex af, af dx_positive ex [sp], hl # hl: y1, sp -> dx ora a # CF: 0 sbc hl, yy # hl: dy = y1 - y0 if full_range_delta jp PO, dy_fits_range jp M, dy_positive jr dy_negative dy_fits_range label end jp P, dy_positive dy_negative cpl # a: -sgn ex af, af neg16 h, l # dy = -dy ex af, af dy_positive ex de, hl # hl: xx or yy, de: dy ex [sp], hl # hl: dx, sp -> xx or yy ex af, af # a': sgn ld a, d ora h jr Z, fits # bit8 fit fitloop srl d rr e # dy = dy / 2 srl h rr l # dx = dx / 2 ld a, d ora h jr NZ, fitloop fits ld h, e # h: dy, l: dx pop de # xx or yy end end
Creates a routine that computes the Outcode bits for the xx, yy point and the clipping region. Stores result in outcode. Used by: Macros.gfx_clip_line.
Modifies: af, outcode.
# File lib/zxlib/gfx/clip.rb, line 104 def gfx_clip_compute_outcode(outcode, xx=bc, yy=de, xmax:ixh, xmin:ixl, ymax:iyh, ymin:iyl, jump_rel:true, subroutine:false) raise ArgumentError unless [bc,de,hl].include?(xx) and [bc,de,hl].include?(yy) and xx != yy xh, xl = xx.split yh, yl = yy.split raise ArgumentError if [xmin, xmax, ymin, ymax, outcode].any? {|mm| [xh,xl,yh,yl,a].include?(mm) } isolate do |eoc| ld outcode, Outcode::INSIDE cmp_i16r xh, xl, 0, xmin, gt: x_fits_xmin, eq: x_fits_xmin, jump_rel:jump_rel set Outcode::LEFT_BIT, outcode if jump_rel jr x_fits_xmax else jp x_fits_xmax end x_fits_xmin cmp_i16r xh, xl, 0, xmax, lt: x_fits_xmax, eq: x_fits_xmax, jump_rel:jump_rel set Outcode::RIGHT_BIT, outcode x_fits_xmax cmp_i16r yh, yl, 0, ymin, gt: y_fits_ymin, eq: y_fits_ymin, jump_rel:jump_rel set Outcode::BOTTOM_BIT, outcode if subroutine ret elsif jump_rel jr eoc else jp eoc end y_fits_ymin label if subroutine cmp_i16r yh, yl, 0, ymax, lt: :ret, eq: :ret, jump_rel:jump_rel else cmp_i16r yh, yl, 0, ymax, lt: eoc, eq: eoc, jump_rel:jump_rel end set Outcode::TOP_BIT, outcode ret if subroutine end end
Converts clipped 16-bit coordinates to the “draw line” routine arguments.
The line endpoints are 16-bit signed integers: x0, y0 and x1, y1.
This routine should be used after clipping the coordinates e.g. with Macros.gfx_clip_line.
- NOTE
-
This routine expects the line endpoints to be clipped (each in the range: 0..255).
xx-
the 16-bit register pair that holds
x1in the main andx0in the alternative set:bcorde. yy-
the 16-bit register pair that holds
y1in the main andy0in the alternative set:deorbc.
When routine is over the alternative and main registers will be swapped.
Option args_type can be one of:
:zxlib-
Prepares arguments for the
Gfx::Draw::Macros.draw_lineroutine (default).
:rom-
Prepares arguments for the
rom.draw_line_1routine (at 0x24BA):h|l: the starting y|x point - should be put into vars.coords b|c: abs(dy)|abs(dx) - absolute distances from the starting point d|e: sgn(dy)|sgn(dx) - the directions (-1 or 1) of the distances
T-states: 24 + ( zxlib: 60 or rom: ~65 [54/65/65/76] )
Modifies: af, af', hl', bc', de', swaps registers: bc, de, hl.
# File lib/zxlib/gfx/clip.rb, line 53 def gfx_clip_coords_to_draw_line_args(xx=bc, yy=de, args_type: :zxlib) raise ArgumentError unless [bc,de].include?(xx) and [bc,de].include?(yy) and xx != yy _, xl = xx.split _, yl = yy.split isolate do |eoc| # xx: x1, yy: y1 ld a, xl # x1 ex af, af ld a, yl # y1 exx # xx: x0, yy: y0 ld h, yl # y0 ld l, xl # x0 case args_type when :zxlib sub h # y1 - y0 ld e, a # dy sbc a ld d, a # d: sgn(dy) xor e sub d ld e, a # e: abs(dy) ex af, af # x1 sub l # x1 - x0 ld c, a # dx sbc a ld b, a # b: sgn(dx) xor c sub b ld c, a # c: abs(dx) when :rom sub h # y1 - y0 ld de, 0x0101 jr NC, dy_pos 2.times { dec d } neg dy_pos ld b, a # abs(dy) ex af, af sub l # x1 - x0 jr NC, dx_pos 2.times { dec e } neg dx_pos ld c, a # abs(dx) else raise ArgumentError, "args_type must be :zxlib or :rom" end end end
Creates a subroutine that clips a single dimension. Used by: Macros.gfx_clip_line.
hl = a0 + sign * d1 * (minmax - b0) / d2
Expects:
-
minmaxina, -
signina'as a boolean: 0x00 for positive and 0xFF for negative.
Modifies: af, af', hl and 3 levels of the machine stack.
# File lib/zxlib/gfx/clip.rb, line 226 def gfx_clip_dimension(a0:bc, b0:de, d1:l, d2:h, full_range_delta:true) raise ArgumentError unless [bc,de].include?(a0) and [bc,de].include?(b0) and a0 != b0 raise ArgumentError unless [h,l].include?(d1) and [h,l].include?(d2) and d1 != d2 a0h, a0l = a0.split b0h, b0l = b0.split minmax = a isolate do push a0 ld a0h, d1 ld a0l, d2 ld l, minmax xor a ld h, a sbc hl, b0 push b0 if full_range_delta jp PO, arg_fits_range jp M, positive_arg jr negative_arg arg_fits_range label end jp P, positive_arg negative_arg ex af, af cpl # a: -sgn ex af, af neg16 h, l positive_arg push a0 mul8_24 h, l, a0h, t:a0l, tt:b0, clrahl:true # a|hl = d1 * (minmax - b0) pop a0 ld b0l, a divmod24_8 b0l, h, l, a0l, check0:false # divide (a|hl) / d2 # TODO check CF, check a0h ex af, af # a: sgn ora a jr Z, positive_result neg16 h, l positive_result pop b0 pop a0 add hl, a0 end end
Creates a subroutine for clipping lines to the rectangle viewport area using Cohen–Sutherland algorithm.
The line endpoints are 16-bit signed integers: x0, y0 and x1, y1. The viewport area is determined by four 8-bit unsigned integers: xmax, xmin, ymax, ymin.
The routine verifies if any part of the line crosses the viewport area. If it does the Z flag is being set to 1. ZF will be 0 if the line does not cross the viewport region. The line endpoints will not be modified if both of them are already in the viewport region.
On input the beginning coordinates: x0, y0 are expected to be held in main Z80 registers, the end coordinates: x1, y1 are expected to be held in the alternative register set (swapped out).
When routine is over the alternative and main registers will be swapped (x1, y1 will be in the main).
xx-
the 16-bit register pair that initially holds
x0in the main andx1in the alternative set:bcorde. yy-
the 16-bit register pair that initially holds
y0in the main andy1in the alternative set:deorbc.
Each of the viewport options: xmax, xmin, ymax and ymin can be either an 8-bit half of index register: ix, iy (dynamic viewports) or a constant, as an 8-bit integer or a label.
-
full_range_deltacan be set tofalsefor a smaller and slightly faster code if you don't
expect values of |x1 - x0| or |y1 - y0| to be exceeding 32767.
-
compactcan be set tofalseto create larger (~143) but slightly faster code.
Modifies: af, af', hl, hl', bc, bc', de, de' and the machine stack.
# File lib/zxlib/gfx/clip.rb, line 295 def gfx_clip_line(xx=bc, yy=de, xmax:ixh, xmin:ixl, ymax:iyh, ymin:iyl, full_range_delta:true, compact:true) raise ArgumentError unless [bc,de].include?(xx) and [bc,de].include?(yy) and xx != yy xh, xl = xx.split yh, yl = yy.split isolate do if compact call compute_outcode ld a, l else gfx_clip_compute_outcode h, xx, yy, xmin:xmin, xmax:xmax, ymin:ymin, ymax:ymax # h: outcode0 ld a, h # outcode0 end exx ld h, a # outcode0 clip_loop1 label if compact call compute_outcode else gfx_clip_compute_outcode l, xx, yy, xmin:xmin, xmax:xmax, ymin:ymin, ymax:ymax # l: outcode1 end ld a, h # outcode0 clip_loop0 ora l # outcode0 | outcode1 ret Z # ZF:1 ready, bc:x1, de:y1, bc':x0, de':y0 ld a, h # outcode0 anda l # outcode0 & outcode1 ret NZ # ZF: 0 nothing to draw, move on # xx: x1, yy: y1, xx': x0, yy': y0, h: outcode0, l: outcode1 gfx_clip_calculate_8bit_dx_dy_exx xx, yy, full_range_delta:full_range_delta # xx: x0, yy: y0, xx': x1, yy': y1, h': outcode0, l': outcode1, h: dy, l: dx, a': sgn(dx)^sgn(dy) ld a, h cp l # dy - dx >= 0 exx jr C, left_right0 # dx > dy # TOP and BOTTOM outcode has priority top_bottom0 ld a, h # outcode0 ora a jr Z, top_bottom1 exx rlca # CF: Outcode::TOP_BIT jr C, clip_area0.top rlca # CF: Outcode::BOTTOM_BIT jr C, clip_area0.bottom anda Outcode::LEFT_MASK<<2 jr NZ, clip_area0.left jp clip_area0.right # LEFT and RIGHT outcode has priority left_right0 ld a, h # outcode0 ora a jr Z, left_right1 exx rrca # CF: Outcode::LEFT_BIT jr C, clip_area0.left rrca # CF: Outcode::RIGHT_BIT jr C, clip_area0.right anda Outcode::TOP_MASK>>2 jr NZ, clip_area0.top jp clip_area0.bottom ns :clip_area0 do {top:ymax, bottom:ymin}.each do |sublabel, yminmax| ns sublabel do ld a, yminmax call clip_x_dimension ld16 xx, hl # x0 = x0 + sgn * dx * (yminmax - y0) / dy ld yh, 0 ld yl, yminmax # y0 = yminmax jp recomputecode0 end end {left:xmin, right:xmax}.each do |sublabel, xminmax| ns sublabel do ld a, xminmax call clip_y_dimension ld16 yy, hl # y0 = y0 + sgn * dy * (xminmax - x0) / dx ld xh, 0 ld xl, xminmax # x0 = xminmax jp recomputecode0 end end end # TOP and BOTTOM outcode has priority top_bottom1 ld a, l # outcode1 exx rlca # CF: Outcode::TOP_BIT jr C, clip_area1.top rlca # CF: Outcode::BOTTOM_BIT jr C, clip_area1.bottom anda Outcode::LEFT_MASK<<2 jr NZ, clip_area1.left jp clip_area1.right # LEFT and RIGHT outcode has priority left_right1 ld a, l # outcode1 exx rrca # CF: Outcode::LEFT_BIT jr C, clip_area1.left rrca # CF: Outcode::RIGHT_BIT jr C, clip_area1.right anda Outcode::TOP_MASK>>2 jr NZ, clip_area1.top jp clip_area1.bottom ns :clip_area1 do {top:ymax, bottom:ymin}.each do |sublabel, yminmax| ns sublabel do ld a, yminmax call clip_x_dimension push hl exx pop xx # x1 = x0 + sgn * dx * (yminmax - y0) / dy ld yh, 0 ld yl, yminmax # y1 = yminmax jp clip_loop1 end end {left:xmin, right:xmax}.each do |sublabel, xminmax| ns sublabel do ld a, xminmax call clip_y_dimension push hl exx pop yy # y1 = y0 + sgn * dy * (xminmax - x0) / dx ld xh, 0 ld xl, xminmax # x1 = xminmax jp clip_loop1 end end end ns :recomputecode0 do if compact call compute_outcode ld a, l else gfx_clip_compute_outcode h, xx, yy, xmin:xmin, xmax:xmax, ymin:ymin, ymax:ymax # h: outcode0 ld a, h # outcode0 end exx ld h, a # outcode0 jp clip_loop0 end # hl = x0 + a' * dx * (a - y0) / dy clip_x_dimension gfx_clip_dimension a0:xx, b0:yy, d1:l, d2:h, full_range_delta:full_range_delta ret # hl = y0 + a' * dy * (a - x0) / dx clip_y_dimension gfx_clip_dimension a0:yy, b0:xx, d1:h, d2:l, full_range_delta:full_range_delta ret if compact compute_outcode gfx_clip_compute_outcode l, xx, yy, xmin:xmin, xmax:xmax, ymin:ymin, ymax:ymax, subroutine:true end end end