diff options
author | Ben Bridle <bridle.benjamin@gmail.com> | 2024-01-06 12:22:17 +1300 |
---|---|---|
committer | Ben Bridle <bridle.benjamin@gmail.com> | 2024-01-06 12:22:17 +1300 |
commit | 03023ea0aa987ec7bbcf2ca5019a1db2dedc3493 (patch) | |
tree | 7d18bbbc400494b522a1b749bae70635e53bffe4 | |
parent | 4735200272ac8a8165bd43784a41ca5b3725bc16 (diff) | |
download | bedrock-pc-03023ea0aa987ec7bbcf2ca5019a1db2dedc3493.zip |
Make orthogonal line drawing operations work with signed coordinates
The vector line drawing operation of the screen device uses optimised
methods when drawing horizontal and vertical lines. The emulator now
interprets the coordinates of these lines as signed values, to allow for
the correct rendering of orthogonal lines where the coordinates of the
left or top edges are off-screen. Diagonal lines are yet to be tackled.
The bounding-box calculations shared by the rectangle and orthogonal
line drawing methods have also been split off into their own method,
significantly reducing the code duplication between all four of the
methods which require this functionality.
This commit also makes aesthetic changes to the code in the
draw_diagonal_line method to improve readability and brevity, as a part
of the changes made to the draw_line method when the points parameter
was removed from each of the three lower line-drawing methods.
-rw-r--r-- | src/devices/screen.rs | 219 |
1 files changed, 100 insertions, 119 deletions
diff --git a/src/devices/screen.rs b/src/devices/screen.rs index ee11f52..c96d1c4 100644 --- a/src/devices/screen.rs +++ b/src/devices/screen.rs @@ -257,28 +257,28 @@ impl ScreenDevice { } fn draw_line(&mut self, colour: u8, layer: ScreenLayer) { - let points = self.vector.get_pair(); - match (points[0].x == points[1].x, points[0].y == points[1].y) { - (false, false) => self.draw_diagonal_line(colour, layer, points), - (false, true) => self.draw_horizontal_line(colour, layer, points), - ( true, false) => self.draw_vertical_line(colour, layer, points), - ( true, true) => self.draw_pixel(colour, layer, points[0]), + let [p0, p1] = self.vector.get_pair(); + match (p0.x == p1.x, p0.y == p1.y) { + (false, false) => self.draw_diagonal_line(colour, layer), + (false, true) => self.draw_horizontal_line(colour, layer), + ( true, false) => self.draw_vertical_line(colour, layer), + ( true, true) => self.draw_pixel(colour, layer, p0), }; } - fn draw_diagonal_line(&mut self, colour: u8, layer: ScreenLayer, points: [ScreenPosition; 2]) { + fn draw_diagonal_line(&mut self, colour: u8, layer: ScreenLayer) { fn abs_diff(v0: u16, v1: u16) -> u16 { let v = v1.wrapping_sub(v0); if v > 0x8000 { !v + 1 } else { v } } - let [p0, p1] = points; + let [p0, p1] = self.vector.get_pair(); // If the slope of the line is greater than 1. if abs_diff(p0.y, p1.y) > abs_diff(p0.x, p1.x) { // Swap points 0 and 1 so that y0 is always smaller than y1. - let (x0, y0, x1, y1) = match points[0].y > points[1].y { - true => (points[1].x, points[1].y, points[0].x, points[0].y), - false => (points[0].x, points[0].y, points[1].x, points[1].y), + let (x0, y0, x1, y1) = match p0.y > p1.y { + true => (p1.x, p1.y, p0.x, p0.y), + false => (p0.x, p0.y, p1.x, p1.y), }; let dy = y1 - y0; @@ -303,9 +303,9 @@ impl ScreenDevice { // If the slope of the line is less than or equal to 1. } else { // Swap points 0 and 1 so that x0 is always smaller than x1. - let (x0, y0, x1, y1) = match points[0].x > points[1].x { - true => (points[1].x, points[1].y, points[0].x, points[0].y), - false => (points[0].x, points[0].y, points[1].x, points[1].y), + let (x0, y0, x1, y1) = match p0.x > p1.x { + true => (p1.x, p1.y, p0.x, p0.y), + false => (p0.x, p0.y, p1.x, p1.y), }; let dx = x1 - x0; @@ -330,125 +330,82 @@ impl ScreenDevice { } } - fn draw_horizontal_line(&mut self, colour: u8, layer: ScreenLayer, points: [ScreenPosition; 2]) { - let [start, end] = points; - let dim = self.dimensions; - let x0 = min(start.x, end.x); - let x1 = max(start.x, end.x); - if (x0 >= dim.width && x1 >= dim.width) || start.y >= dim.height { return } - let x0 = min(x0, dim.width.saturating_sub(1)); - let x1 = min(x1, dim.width.saturating_sub(1)); - let row_i = (dim.width as usize) * (start.y as usize); - let start_i = row_i + x0 as usize; - let end_i = row_i + x1 as usize; - let layer = match layer { - ScreenLayer::Background => &mut self.background, - ScreenLayer::Foreground => &mut self.foreground, - }; - layer[start_i..=end_i].fill(colour); - return + fn draw_horizontal_line(&mut self, colour: u8, layer: ScreenLayer) { + if let Some([x0, y, x1, _]) = self.find_vector_bounding_box() { + let screen_width = self.dimensions.width as usize; + let i = screen_width * y; + let layer = match layer { + ScreenLayer::Background => &mut self.background, + ScreenLayer::Foreground => &mut self.foreground, + }; + layer[i+x0..=i+x1].fill(colour); + } } - fn draw_vertical_line(&mut self, colour: u8, layer: ScreenLayer, points: [ScreenPosition; 2]) { - let [start, end] = points; - let dim = self.dimensions; - let y0 = min(start.y, end.y); - let y1 = max(start.y, end.y); - if (y0 >= dim.height && y1 >= dim.height) || start.x >= dim.width { return } - let y0 = min(y0, dim.height.saturating_sub(1)); - let y1 = min(y1, dim.height.saturating_sub(1)); - let mut i = (start.x as usize) + (dim.width as usize * (y0 as usize)); - let pixels = match layer { - ScreenLayer::Background => &mut self.background, - ScreenLayer::Foreground => &mut self.foreground, - }; - for _ in y0..=y1 { - pixels[i] = colour; - i += dim.width as usize; + fn draw_vertical_line(&mut self, colour: u8, layer: ScreenLayer) { + if let Some([x, y0, _, y1]) = self.find_vector_bounding_box() { + let screen_width = self.dimensions.width as usize; + let mut i = (screen_width * y0) + x; + let layer = match layer { + ScreenLayer::Background => &mut self.background, + ScreenLayer::Foreground => &mut self.foreground, + }; + for _ in y0..=y1 { + layer[i] = colour; + i += screen_width; + } + } - return } fn draw_rect(&mut self, colour: u8, layer: ScreenLayer) { - let [x0, y0, x1, y1] = { - macro_rules! raise {($v:expr) => {$v.wrapping_add(0x8000)};} - macro_rules! lower {($v:expr) => {$v.wrapping_sub(0x8000)};} - let [p0, p1] = self.vector.get_pair(); - let [p0x, p0y] = [ raise!(p0.x), raise!(p0.y) ]; - let [p1x, p1y] = [ raise!(p1.x), raise!(p1.y) ]; - let [x0, y0] = [ min(p0x, p1x), min(p0y, p1y) ]; - let [x1, y1] = [ max(p0x, p1x), max(p0y, p1y) ]; - let right = self.dimensions.width.saturating_sub(1); - let bottom = self.dimensions.height.saturating_sub(1); - if x0 > raise!(right) || y0 > raise!(bottom) || x1 < 0x8000 || y1 < 0x8000 { return } - [ - if x0 < 0x8000 { 0 } else { min(lower!(x0), right) } as usize, - if y0 < 0x8000 { 0 } else { min(lower!(y0), bottom) } as usize, - if x1 < 0x8000 { 0 } else { min(lower!(x1), right) } as usize, - if y1 < 0x8000 { 0 } else { min(lower!(y1), bottom) } as usize, - ] - }; - let screen_width = self.dimensions.width as usize; - let rect_width = x1 - x0 + 1; - let mut i = x0 + (screen_width * y0); - let pixels = match layer { - ScreenLayer::Background => &mut self.background, - ScreenLayer::Foreground => &mut self.foreground, - }; - for _ in y0..=y1 { - pixels[i..i+rect_width].fill(colour); - i += screen_width; + if let Some([x0, y0, x1, y1]) = self.find_vector_bounding_box() { + let screen_width = self.dimensions.width as usize; + let rect_width = x1 - x0 + 1; + let mut i = x0 + (screen_width * y0); + let pixels = match layer { + ScreenLayer::Background => &mut self.background, + ScreenLayer::Foreground => &mut self.foreground, + }; + for _ in y0..=y1 { + pixels[i..i+rect_width].fill(colour); + i += screen_width; + } } } fn draw_rect_1bit(&mut self, params: u8, layer: ScreenLayer) { - let [x0, y0, x1, y1] = { - macro_rules! raise {($v:expr) => {$v.wrapping_add(0x8000)};} - macro_rules! lower {($v:expr) => {$v.wrapping_sub(0x8000)};} - let [p0, p1] = self.vector.get_pair(); - let [p0x, p0y] = [ raise!(p0.x), raise!(p0.y) ]; - let [p1x, p1y] = [ raise!(p1.x), raise!(p1.y) ]; - let [x0, y0] = [ min(p0x, p1x), min(p0y, p1y) ]; - let [x1, y1] = [ max(p0x, p1x), max(p0y, p1y) ]; - let right = self.dimensions.width.saturating_sub(1); - let bottom = self.dimensions.height.saturating_sub(1); - if x0 > raise!(right) || y0 > raise!(bottom) || x1 < 0x8000 || y1 < 0x8000 { return } - [ - if x0 < 0x8000 { 0 } else { min(lower!(x0), right) } as usize, - if y0 < 0x8000 { 0 } else { min(lower!(y0), bottom) } as usize, - if x1 < 0x8000 { 0 } else { min(lower!(x1), right) } as usize, - if y1 < 0x8000 { 0 } else { min(lower!(y1), bottom) } as usize, - ] - }; - let screen_width = self.dimensions.width as usize; - let rect_width = x1 - x0 + 1; - let mut i = x0 + (screen_width * y0); - let pixels = match layer { - ScreenLayer::Background => &mut self.background, - ScreenLayer::Foreground => &mut self.foreground, - }; + if let Some([x0, y0, x1, y1]) = self.find_vector_bounding_box() { + let screen_width = self.dimensions.width as usize; + let rect_width = x1 - x0 + 1; + let mut i = x0 + (screen_width * y0); + let pixels = match layer { + ScreenLayer::Background => &mut self.background, + ScreenLayer::Foreground => &mut self.foreground, + }; - let sprite_data = self.sprite_data.get_1bit_sprite(); - let mut sprite_i = y0 % 8; - let sprite_x_off = (x0 % 8) as u32; - let transparent = params & 0x08 != 0; - if params & 0x07 != 0 { - todo!("Pre-treat sprite, with rotation/translation"); - } + let sprite_data = self.sprite_data.get_1bit_sprite(); + let mut sprite_i = y0 % 8; + let sprite_x_off = (x0 % 8) as u32; + let transparent = params & 0x08 != 0; + if params & 0x07 != 0 { + todo!("Pre-treat sprite, with rotation/translation"); + } - for _ in y0..=y1 { - let mut row = sprite_data[sprite_i].rotate_left(sprite_x_off); - for _ in x0..=x1 { - let colour = (row >> 7) as usize; - if !(transparent && colour == 0) { - pixels[i] = self.sprite_colours[colour]; + for _ in y0..=y1 { + let mut row = sprite_data[sprite_i].rotate_left(sprite_x_off); + for _ in x0..=x1 { + let colour = (row >> 7) as usize; + if !(transparent && colour == 0) { + pixels[i] = self.sprite_colours[colour]; + } + row = row.rotate_left(1); + i += 1; } - row = row.rotate_left(1); - i += 1; + sprite_i = (sprite_i + 1) % 8; + i += screen_width - rect_width; } - sprite_i = (sprite_i + 1) % 8; - i += screen_width - rect_width; - } + }; } fn draw_sprite(&mut self, params: u8, layer: ScreenLayer, sprite: [u8; 64]) { @@ -484,4 +441,28 @@ impl ScreenDevice { _ => unreachable!(), } } + + /// Returns [x0, y0, x1, y1] + fn find_vector_bounding_box(&self) -> Option<[usize; 4]> { + macro_rules! raise {($v:expr) => {$v.wrapping_add(0x8000)};} + macro_rules! lower {($v:expr) => {$v.wrapping_sub(0x8000)};} + + let [p0, p1] = self.vector.get_pair(); + let [p0x, p0y] = [ raise!(p0.x), raise!(p0.y) ]; + let [p1x, p1y] = [ raise!(p1.x), raise!(p1.y) ]; + let [x0, y0] = [ min(p0x, p1x), min(p0y, p1y) ]; + let [x1, y1] = [ max(p0x, p1x), max(p0y, p1y) ]; + let right = self.dimensions.width.saturating_sub(1); + let bottom = self.dimensions.height.saturating_sub(1); + if x0 > raise!(right) || y0 > raise!(bottom) || x1 < 0x8000 || y1 < 0x8000 { + None + } else { + Some([ + if x0 < 0x8000 { 0 } else { min(lower!(x0), right) } as usize, + if y0 < 0x8000 { 0 } else { min(lower!(y0), bottom) } as usize, + if x1 < 0x8000 { 0 } else { min(lower!(x1), right) } as usize, + if y1 < 0x8000 { 0 } else { min(lower!(y1), bottom) } as usize, + ]) + } + } } |