diff --git a/src/memory.rs b/src/memory.rs index d679ad0..30b1f00 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -98,8 +98,11 @@ macro_rules! primitive_memory_value_impl { } primitive_memory_value_impl!(u8, 1); +primitive_memory_value_impl!(i8, 2); primitive_memory_value_impl!(u16, 2); +primitive_memory_value_impl!(i16, 2); primitive_memory_value_impl!(u32, 4); +primitive_memory_value_impl!(i32, 2); impl MemoryValue for [T; N] { #[inline] diff --git a/src/window/utils.rs b/src/window/utils.rs index ec3fe98..d53f6b4 100644 --- a/src/window/utils.rs +++ b/src/window/utils.rs @@ -34,10 +34,11 @@ pub trait UiExt { impl UiExt for Ui { fn section(&mut self, title: impl Into, add_contents: impl FnOnce(&mut Ui)) { + let title: String = title.into(); let mut frame = Frame::group(self.style()); frame.outer_margin.top += 10.0; frame.inner_margin.top += 2.0; - let res = frame.show(self, add_contents); + let res = self.push_id(&title, |ui| frame.show(ui, add_contents)); let text = RichText::new(title).background_color(self.style().visuals.panel_fill); let old_rect = res.response.rect; let mut text_rect = old_rect; @@ -133,12 +134,14 @@ impl<'a, T: Number> NumberEdit<'a, T> { impl Widget for NumberEdit<'_, T> { fn ui(self, ui: &mut Ui) -> Response { + let id = ui.id(); + let (last_value, mut str, focus) = ui.memory(|m| { let (lv, s) = m .data - .get_temp(ui.id()) + .get_temp(id) .unwrap_or((*self.value, self.value.to_string())); - let focus = m.has_focus(ui.id()); + let focus = m.has_focus(id); (lv, s, focus) }); let mut stale = false; @@ -174,7 +177,7 @@ impl Widget for NumberEdit<'_, T> { } let text = TextEdit::singleline(&mut str) .horizontal_align(Align::Max) - .id(ui.id()) + .id(id) .margin(Margin { left: 4.0, right: 20.0, @@ -242,7 +245,7 @@ impl Widget for NumberEdit<'_, T> { stale = true; } if stale { - ui.memory_mut(|m| m.data.insert_temp(ui.id(), (*self.value, str))); + ui.memory_mut(|m| m.data.insert_temp(id, (*self.value, str))); } res } diff --git a/src/window/vram/world.rs b/src/window/vram/world.rs index 18803cf..f9e9a61 100644 --- a/src/window/vram/world.rs +++ b/src/window/vram/world.rs @@ -24,7 +24,9 @@ pub struct WorldWindow { sim_id: SimId, loader: Arc, worlds: MemoryView, + bgmaps: MemoryView, index: usize, + param_index: usize, generic_palette: bool, params: VramParams, scale: f32, @@ -45,7 +47,9 @@ impl WorldWindow { sim_id, loader: Arc::new(loader), worlds: memory.watch(sim_id, 0x3d800, 0x400), + bgmaps: memory.watch(sim_id, 0x00020000, 0x20000), index: params.index, + param_index: 0, generic_palette: params.generic_palette, params, scale: 1.0, @@ -262,6 +266,57 @@ impl WorldWindow { }); }); }); + if world.header.mode == WorldMode::HBias { + ui.section("H-bias", |ui| { + TableBuilder::new(ui) + .column(Column::remainder()) + .column(Column::remainder()) + .body(|mut body| { + body.row(row_height, |mut row| { + row.col(|ui| { + ui.label("Index"); + }); + row.col(|ui| { + ui.add(NumberEdit::new(&mut self.param_index).range(0..32)); + }); + }); + let base = world.param_base + self.param_index * 2; + let mut param = HBiasParam::load(&self.bgmaps.borrow(), base); + body.row(row_height, |mut row| { + row.col(|ui| { + ui.label("Address"); + }); + row.col(|ui| { + let address = 0x00020000 + base * 2; + let mut address_str = format!("{address:08x}"); + ui.add_enabled( + false, + TextEdit::singleline(&mut address_str) + .horizontal_align(Align::Max), + ); + }); + }); + body.row(row_height, |mut row| { + row.col(|ui| { + ui.label("Left"); + }); + row.col(|ui| { + ui.add(NumberEdit::new(&mut param.left).range(-4096..4096)); + }); + }); + body.row(row_height, |mut row| { + row.col(|ui| { + ui.label("Right"); + }); + row.col(|ui| { + ui.add(NumberEdit::new(&mut param.right).range(-4096..4096)); + }); + }); + }); + }); + } else { + self.param_index = 0; + } ui.section("Display", |ui| { ui.horizontal(|ui| { ui.label("Scale"); @@ -497,48 +552,44 @@ impl WorldRenderer { ] }; - let mut chars = CharCache::new(self.chardata.borrow()); - let mut cells = CellCache::new(self.bgmaps.borrow()); + let chardata = self.chardata.borrow(); + let bgmaps = self.bgmaps.borrow(); + let mut chars = [CharCache::new(&chardata), CharCache::new(&chardata)]; + let mut cells = [CellCache::new(&bgmaps), CellCache::new(&bgmaps)]; + let mut source = SourceCoordCalculator::new(&bgmaps, &world); for y in 0..height { let dy = y + world.dst_y; if !(0..224).contains(&dy) { continue; } - let sy = y + world.src_y; - // left side for x in 0..world.width { let dx = x + world.dst_x - world.dst_parallax; - if !(0..384).contains(&dx) { - continue; + if world.header.lon && (0..384).contains(&dx) { + let (sx, sy) = source.left(x, y); + + let cell_index = world.source_cell(sx, sy); + let cell = cells[0].get(cell_index); + let char = chars[0].get(cell.char_index); + let row = (sy & 0x7) as usize; + let col = (sx & 0x7) as usize; + let pixel = utils::read_char_pixel(char, cell.hflip, cell.vflip, row, col); + image.add((dx as usize, dy as usize), colors[0][pixel as usize]); } - let sx = x + world.src_x - world.src_parallax; - let cell_index = world.source_cell(sx, sy); - let cell = cells.get(cell_index); - let char = chars.get(cell.char_index); - let row = (sy & 0x7) as usize; - let col = (sx & 0x7) as usize; - let pixel = utils::read_char_pixel(char, cell.hflip, cell.vflip, row, col); - image.add((dx as usize, dy as usize), colors[0][pixel as usize]); - } - - // right side - for x in 0..world.width { let dx = x + world.dst_x + world.dst_parallax; - if !(0..384).contains(&dx) { - continue; - } - let sx = x + world.src_x + world.src_parallax; + if world.header.ron && (0..384).contains(&dx) { + let (sx, sy) = source.right(x, y); - let cell_index = world.source_cell(sx, sy); - let cell = cells.get(cell_index); - let char = chars.get(cell.char_index); - let row = (sy & 0x7) as usize; - let col = (sx & 0x7) as usize; - let pixel = utils::read_char_pixel(char, cell.hflip, cell.vflip, row, col); - image.add((dx as usize, dy as usize), colors[1][pixel as usize]); + let cell_index = world.source_cell(sx, sy); + let cell = cells[1].get(cell_index); + let char = chars[1].get(cell.char_index); + let row = (sy & 0x7) as usize; + let col = (sx & 0x7) as usize; + let pixel = utils::read_char_pixel(char, cell.hflip, cell.vflip, row, col); + image.add((dx as usize, dy as usize), colors[1][pixel as usize]); + } } } } @@ -556,7 +607,7 @@ impl VramRenderer<1> for WorldRenderer { let worlds = self.worlds.borrow(); let header = WorldHeader::parse(worlds.read(params.index * 16)); - if header.end || (!header.lon && header.ron) { + if header.end || (!header.lon && !header.ron) { image.clear(); return; } @@ -693,13 +744,13 @@ impl Display for WorldMode { } struct CellCache<'a> { - bgmaps: MemoryRef<'a>, + bgmaps: &'a MemoryRef<'a>, index: usize, cell: CellData, } impl<'a> CellCache<'a> { - fn new(bgmaps: MemoryRef<'a>) -> Self { + fn new(bgmaps: &'a MemoryRef<'a>) -> Self { Self { bgmaps, index: 0x10000, @@ -718,13 +769,13 @@ impl<'a> CellCache<'a> { } struct CharCache<'a> { - chardata: MemoryRef<'a>, + chardata: &'a MemoryRef<'a>, index: usize, char: [u16; 8], } impl<'a> CharCache<'a> { - fn new(chardata: MemoryRef<'a>) -> Self { + fn new(chardata: &'a MemoryRef<'a>) -> Self { Self { chardata, index: 2048, @@ -740,3 +791,82 @@ impl<'a> CharCache<'a> { &self.char } } + +struct SourceCoordCalculator<'a> { + params: &'a MemoryRef<'a>, + world: &'a World, + y: i16, + param: SourceParam, +} + +impl<'a> SourceCoordCalculator<'a> { + fn new(params: &'a MemoryRef<'a>, world: &'a World) -> Self { + Self { + params, + world, + y: -1, + param: SourceParam::Normal, + } + } + + fn left(&mut self, x: i16, y: i16) -> (i16, i16) { + self.update_param(y); + match &self.param { + SourceParam::HBias(HBiasParam { left, .. }) => { + let sx = x + self.world.src_x - self.world.src_parallax + *left; + let sy = y + self.world.src_y; + (sx, sy) + } + SourceParam::Normal => { + let sx = x + self.world.src_x - self.world.src_parallax; + let sy = y + self.world.src_y; + (sx, sy) + } + } + } + + fn right(&mut self, x: i16, y: i16) -> (i16, i16) { + self.update_param(y); + match &self.param { + SourceParam::HBias(HBiasParam { right, .. }) => { + let sx = x + self.world.src_x + self.world.src_parallax + *right; + let sy = y + self.world.src_y; + (sx, sy) + } + SourceParam::Normal => { + let sx = x + self.world.src_x + self.world.src_parallax; + let sy = y + self.world.src_y; + (sx, sy) + } + } + } + + fn update_param(&mut self, y: i16) { + if self.y == y { + return; + } + if self.world.header.mode == WorldMode::HBias { + let base = self.world.param_base + (2 * y as usize); + self.param = SourceParam::HBias(HBiasParam::load(self.params, base)); + } + self.y = y; + } +} + +enum SourceParam { + Normal, + HBias(HBiasParam), +} + +struct HBiasParam { + left: i16, + right: i16, +} + +impl HBiasParam { + fn load(params: &MemoryRef, index: usize) -> Self { + let left = params.read::(index) << 3 >> 3; + let right = params.read::(index | 1) << 3 >> 3; + Self { left, right } + } +}