implement basic land bordering
authoralfadur
Thu, 08 Nov 2018 07:15:22 +0300
changeset 14191 a4c1a2d0ac24
parent 14190 e2c51c8e0b2e
child 14192 9c87addd10e6
implement basic land bordering
rust/land2d/src/lib.rs
rust/mapgen/src/lib.rs
rust/mapgen/src/theme.rs
rust/vec2d/src/lib.rs
--- a/rust/land2d/src/lib.rs	Thu Nov 08 00:00:58 2018 +0100
+++ b/rust/land2d/src/lib.rs	Thu Nov 08 07:15:22 2018 +0300
@@ -84,7 +84,7 @@
     }
 
     #[inline]
-    pub fn rows(&self) -> impl Iterator<Item = &[T]> {
+    pub fn rows(&self) -> impl DoubleEndedIterator<Item = &[T]> {
         self.pixels.rows()
     }
 
--- a/rust/mapgen/src/lib.rs	Thu Nov 08 00:00:58 2018 +0100
+++ b/rust/mapgen/src/lib.rs	Thu Nov 08 07:15:22 2018 +0300
@@ -108,6 +108,7 @@
 
     pub fn make_texture(&self, land: &Land2D<u8>, theme: &Theme) -> Vec2D<u32> {
         let mut texture = Vec2D::new(land.size(), 0);
+
         if let Some(land_sprite) = theme.land_texture() {
             for (row_index, (land_row, tex_row)) in land.rows()
                 .zip(texture.rows_mut())
@@ -136,10 +137,106 @@
                 }
             }
         }
+
+        if let Some(border_sprite) = theme.border_texture() {
+            assert!(border_sprite.height() <= 512);
+            let border_width = (border_sprite.height() / 2) as u8;
+
+            let mut offsets = vec![255u8; land.width()];
+
+            land_border_pass(
+                land.rows().rev().zip(texture.rows_mut().rev()),
+                &mut offsets,
+                border_width,
+                |x, y| border_sprite.get_pixel(
+                    x % border_sprite.width(),
+                    border_sprite.height() - 1 - y,
+                )
+            );
+
+            offsets.iter_mut().for_each(|v| *v = 255);
+
+            land_border_pass(
+                land.rows().zip(texture.rows_mut()),
+                &mut offsets,
+                border_width,
+                |x, y| border_sprite.get_pixel(
+                    x % border_sprite.width(),
+                    y,
+                )
+            );
+        }
+
         texture
     }
 }
 
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+struct Color(u32);
+
+impl Color {
+    #[inline]
+    fn red(self) -> u8 {
+        (self.0 >> 0 & 0xFF) as u8
+    }
+
+    #[inline]
+    fn green(self) -> u8 {
+        (self.0 >> 8 & 0xFF) as u8
+    }
+
+    #[inline]
+    fn blue(self) -> u8 {
+        (self.0 >> 16 & 0xFF) as u8
+    }
+
+    #[inline]
+    fn alpha(self) -> u8 {
+        (self.0 >> 24 & 0xFF) as u8
+    }
+}
+
+#[inline]
+fn lerp(from: u8, to: u8, coef: u8) -> u8 {
+    ((from as u16 * (256 - coef as u16) + to as u16 * coef as u16) / 256) as u8
+}
+
+#[inline]
+fn blend(source: u32, target: u32) -> u32 {
+    let source = Color(source);
+    let target = Color(target);
+    let alpha = lerp(target.alpha(), 255, source.alpha());
+    let red = lerp(target.red(), source.red(), source.alpha());
+    let green = lerp(target.green(), source.green(), source.alpha());
+    let blue = lerp(target.blue(), source.blue(), source.alpha());
+    (red as u32) << 0 | (green as u32) << 8 | (blue as u32) << 16 | (alpha as u32) << 24
+}
+
+fn land_border_pass<'a, T, F>(rows: T, offsets: &mut [u8], border_width: u8, pixel_getter: F)
+    where T: Iterator<Item = (&'a [u8], &'a mut [u32])>,
+          F: (Fn(usize, usize) -> u32)
+{
+    for (land_row, tex_row) in rows {
+        for (x, ((land_v, tex_v), offset_v)) in land_row.iter()
+            .zip(tex_row.iter_mut())
+            .zip(offsets.iter_mut())
+            .enumerate()
+        {
+            *offset_v = if *land_v == 0 {
+                if *offset_v < border_width {
+                    *tex_v = blend(
+                        pixel_getter(x, *offset_v as usize),
+                        *tex_v,
+                    )
+                }
+                offset_v.saturating_add(1)
+            } else {
+                0
+            }
+        }
+    }
+}
+
 fn tex_row_copy(land_row: &[u8], tex_row: &mut [u32], sprite_row: &[u32]) {
     for ((land_v, tex_v), sprite_v) in
         land_row.iter().zip(tex_row.iter_mut()).zip(sprite_row)
--- a/rust/mapgen/src/theme.rs	Thu Nov 08 00:00:58 2018 +0100
+++ b/rust/mapgen/src/theme.rs	Thu Nov 08 07:15:22 2018 +0300
@@ -38,7 +38,7 @@
     }
 
     #[inline]
-    pub fn rows(&self) -> impl Iterator<Item = &[u32]> {
+    pub fn rows(&self) -> impl DoubleEndedIterator<Item = &[u32]> {
         self.pixels.rows()
     }
 
@@ -46,16 +46,26 @@
     pub fn get_row(&self, index: usize) -> &[u32] {
         &self.pixels[index]
     }
+
+    #[inline]
+    pub fn get_pixel(&self, x: usize, y: usize) -> u32 {
+        self.pixels[y][x]
+    }
 }
 
 pub struct Theme {
-    land_texture: Option<ThemeSprite>
+    land_texture: Option<ThemeSprite>,
+    border_texture: Option<ThemeSprite>
 }
 
 impl Theme {
     pub fn land_texture(&self) -> Option<&ThemeSprite> {
         self.land_texture.as_ref()
     }
+
+    pub fn border_texture(&self) -> Option<&ThemeSprite> {
+        self.border_texture.as_ref()
+    }
 }
 
 #[derive(Debug)]
@@ -80,7 +90,8 @@
 impl Theme {
     pub fn new() -> Self {
         Theme {
-            land_texture: None
+            land_texture: None,
+            border_texture: None,
         }
     }
 
@@ -90,23 +101,9 @@
         for entry in read_dir(path)? {
             let file = entry?;
             if file.file_name() == "LandTex.png" {
-                let decoder = Decoder::new(
-                    BufReader::new(File::open(file.path())?));
-                let (info, mut reader) = decoder.read_info()?;
-
-                if info.color_type != ColorType::RGBA {
-                    return Err(ThemeLoadError::Format(
-                        format!("Unexpected format: {:?}", info.color_type)));
-                }
-                let size = Size::new(info.width as usize, info.height as usize);
-
-                let mut buffer: Vec2D<u32> = Vec2D::new(size, 0);
-                reader.next_frame(slice_u32_to_u8_mut(buffer.as_mut_slice()))?;
-
-                let land_tex = ThemeSprite {
-                    pixels: buffer
-                };
-                theme.land_texture = Some(land_tex)
+                theme.land_texture = Some(load_sprite(&file.path())?)
+            } else if file.file_name() == "Border.png" {
+                theme.border_texture = Some(load_sprite(&file.path())?)
             }
         }
 
@@ -114,6 +111,23 @@
     }
 }
 
+fn load_sprite(path: &Path) -> Result<ThemeSprite, ThemeLoadError> {
+    let decoder = Decoder::new(
+        BufReader::new(File::open(path)?));
+    let (info, mut reader) = decoder.read_info()?;
+
+    if info.color_type != ColorType::RGBA {
+        return Err(ThemeLoadError::Format(
+            format!("Unexpected format: {:?}", info.color_type)));
+    }
+    let size = Size::new(info.width as usize, info.height as usize);
+
+    let mut pixels: Vec2D<u32> = Vec2D::new(size, 0);
+    reader.next_frame(slice_u32_to_u8_mut(pixels.as_mut_slice()))?;
+
+    Ok(ThemeSprite { pixels })
+}
+
 pub fn slice_u32_to_u8(slice_u32: &[u32]) -> &[u8] {
     unsafe {
         from_raw_parts::<u8>(
--- a/rust/vec2d/src/lib.rs	Thu Nov 08 00:00:58 2018 +0100
+++ b/rust/vec2d/src/lib.rs	Thu Nov 08 07:15:22 2018 +0300
@@ -86,12 +86,12 @@
     }
 
     #[inline]
-    pub fn rows(&self) -> impl Iterator<Item = &[T]> {
+    pub fn rows(&self) -> impl DoubleEndedIterator<Item = &[T]> {
         self.data.chunks(self.width())
     }
 
     #[inline]
-    pub fn rows_mut(&mut self) -> impl Iterator<Item = &mut [T]> {
+    pub fn rows_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut [T]> {
         let width = self.width();
         self.data.chunks_mut(width)
     }