summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Bridle <bridle.benjamin@gmail.com>2024-01-06 12:22:17 +1300
committerBen Bridle <bridle.benjamin@gmail.com>2024-01-06 12:22:17 +1300
commit03023ea0aa987ec7bbcf2ca5019a1db2dedc3493 (patch)
tree7d18bbbc400494b522a1b749bae70635e53bffe4
parent4735200272ac8a8165bd43784a41ca5b3725bc16 (diff)
downloadbedrock-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.rs219
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,
+ ])
+ }
+ }
}