rust/mapgen/src/lib.rs
author Wuzzy <Wuzzy2@mail.ru>
Thu, 11 Jul 2019 16:24:09 +0200
changeset 15252 c10e9261ab9c
parent 14731 946df0bb3b28
child 15850 44b49f255e31
permissions -rw-r--r--
Make lowest line of Splash image frames transparent to work around scaling issues The Splash image is scaled. Sometimes, the lowest line is repeated on the top, which caused some weird lines to appear above big splashes (e.g. piano). This has been done fully automated with a script. Only the alpha channel was changed. The color information is preserved.

pub mod theme;

use self::theme::Theme;
use integral_geometry::{Point, Rect, Size};
use land2d::Land2D;
use landgen::outline_template::OutlineTemplate;
use rand::{thread_rng, Rng};
use serde_derive::Deserialize;
use serde_yaml;
use std::{borrow::Borrow, collections::hash_map::HashMap, mem::replace};
use vec2d::Vec2D;

#[derive(Deserialize)]
struct PointDesc {
    x: u32,
    y: u32,
}

#[derive(Deserialize)]
struct RectDesc {
    x: u32,
    y: u32,
    w: u32,
    h: u32,
}

#[derive(Deserialize)]
struct TemplateDesc {
    width: usize,
    height: usize,
    can_flip: bool,
    can_invert: bool,
    can_mirror: bool,
    is_negative: bool,
    put_girders: bool,
    max_hedgehogs: u8,
    outline_points: Vec<Vec<RectDesc>>,
    fill_points: Vec<PointDesc>,
}

#[derive(Deserialize)]
struct TemplateCollectionDesc {
    templates: Vec<TemplateDesc>,
    template_types: HashMap<String, Vec<usize>>,
}

impl From<&TemplateDesc> for OutlineTemplate {
    fn from(desc: &TemplateDesc) -> Self {
        OutlineTemplate {
            islands: desc
                .outline_points
                .iter()
                .map(|v| {
                    v.iter()
                        .map(|r| {
                            Rect::from_size(
                                Point::new(r.x as i32, r.y as i32),
                                Size::new(r.w as usize, r.h as usize),
                            )
                        })
                        .collect()
                })
                .collect(),
            fill_points: desc
                .fill_points
                .iter()
                .map(|p| Point::new(p.x as i32, p.y as i32))
                .collect(),
            size: Size::new(desc.width, desc.height),
            can_flip: desc.can_flip,
            can_invert: desc.can_invert,
            can_mirror: desc.can_mirror,
            is_negative: desc.is_negative,
        }
    }
}

#[derive(PartialEq, Eq, Hash, Clone, Debug)]
struct TemplateType(String);

impl Borrow<str> for TemplateType {
    fn borrow(&self) -> &str {
        self.0.as_str()
    }
}

#[derive(Debug)]
pub struct MapGenerator {
    pub(crate) templates: HashMap<TemplateType, Vec<OutlineTemplate>>,
}

impl MapGenerator {
    pub fn new() -> Self {
        Self {
            templates: HashMap::new(),
        }
    }

    pub fn import_yaml_templates(&mut self, text: &str) {
        let mut desc: TemplateCollectionDesc = serde_yaml::from_str(text).unwrap();
        let templates = replace(&mut desc.templates, vec![]);
        self.templates = desc
            .template_types
            .into_iter()
            .map(|(size, indices)| {
                (
                    TemplateType(size),
                    indices.iter().map(|i| (&templates[*i]).into()).collect(),
                )
            })
            .collect();
    }

    pub fn get_template(&self, template_type: &str) -> Option<&OutlineTemplate> {
        self.templates
            .get(template_type)
            .and_then(|t| thread_rng().choose(t))
    }

    pub fn make_texture<LandT>(&self, land: &Land2D<LandT>, theme: &Theme) -> Vec2D<u32>
    where
        LandT: Copy + Default + PartialEq,
    {
        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()).enumerate()
            {
                let sprite_row = land_sprite.get_row(row_index % land_sprite.height());
                let mut x_offset = 0;
                while sprite_row.len() < land.width() - x_offset {
                    let copy_range = x_offset..x_offset + sprite_row.len();
                    tex_row_copy(
                        &land_row[copy_range.clone()],
                        &mut tex_row[copy_range],
                        sprite_row,
                    );

                    x_offset += land_sprite.width()
                }

                if x_offset < land.width() {
                    let final_range = x_offset..land.width();
                    tex_row_copy(
                        &land_row[final_range.clone()],
                        &mut tex_row[final_range],
                        &sprite_row[..land.width() - x_offset],
                    );
                }
            }
        }

        if let Some(border_sprite) = theme.border_texture() {
            assert!(border_sprite.height() <= 512);
            let border_width = (border_sprite.height() / 2) as u8;
            let border_sprite = border_sprite.to_tiled();

            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, LandT, T, F>(rows: T, offsets: &mut [u8], border_width: u8, pixel_getter: F)
where
    LandT: Default + PartialEq + 'a,
    T: Iterator<Item = (&'a [LandT], &'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 == LandT::default() {
                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<LandT>(land_row: &[LandT], tex_row: &mut [u32], sprite_row: &[u32])
where
    LandT: Default + PartialEq,
{
    for ((land_v, tex_v), sprite_v) in land_row.iter().zip(tex_row.iter_mut()).zip(sprite_row) {
        *tex_v = if *land_v == LandT::default() {
            *sprite_v
        } else {
            0
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::{MapGenerator, TemplateType};

    #[test]
    fn simple_load() {
        let text = r#"
# comment

templates:
  -
    width: 3072
    height: 1424
    can_flip: false
    can_invert: false
    can_mirror: true
    is_negative: false
    put_girders: true
    max_hedgehogs: 18
    outline_points:
      -
        - {x: 748, y: 1424, w: 1, h: 1}
        - {x: 636, y: 1252, w: 208, h: 72}
        - {x: 898, y: 1110, w: 308, h: 60}
        - {x: 1128, y: 1252, w: 434, h: 40}
        - {x: 1574, y: 1112, w: 332, h: 40}
        - {x: 1802, y: 1238, w: 226, h: 36}
        - {x: 1930, y: 1424, w: 1, h: 1}
    fill_points:
      - {x: 1023, y: 0}
      - {x: 1023, y: 0}

template_types:
    test: [0]
"#;

        let mut generator = MapGenerator::new();
        generator.import_yaml_templates(&text);

        assert!(generator
            .templates
            .contains_key(&TemplateType("test".to_string())));

        let template = generator.get_template("test").unwrap();

        assert_eq!(template.islands[0].len(), 7);
    }
}