use std::{fmt::Display, sync::Arc}; use egui::{ Align, CentralPanel, Checkbox, Color32, ComboBox, Context, Image, ScrollArea, Slider, TextEdit, TextureOptions, Ui, ViewportBuilder, ViewportId, }; use egui_extras::{Column, Size, StripBuilder, TableBuilder}; use fixed::{ types::extra::{U3, U9}, FixedI32, }; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; use crate::{ emulator::SimId, images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader}, memory::{MemoryClient, MemoryRef, MemoryView}, window::{ utils::{NumberEdit, UiExt as _}, AppWindow, }, }; use super::utils::{self, shade, CellData, Object}; pub struct WorldWindow { sim_id: SimId, loader: Arc, memory: Arc, worlds: MemoryView, bgmaps: MemoryView, index: usize, param_index: usize, generic_palette: bool, params: ImageParams, scale: f32, } impl WorldWindow { pub fn new(sim_id: SimId, memory: &Arc, images: &mut ImageProcessor) -> Self { let initial_params = WorldParams { index: 31, generic_palette: false, left_color: Color32::from_rgb(0xff, 0x00, 0x00), right_color: Color32::from_rgb(0x00, 0xc6, 0xf0), }; let renderer = WorldRenderer::new(sim_id, memory); let ([world], params) = images.add(renderer, initial_params); let loader = ImageTextureLoader::new([("vip://world".into(), world)]); Self { sim_id, loader: Arc::new(loader), memory: memory.clone(), worlds: memory.watch(sim_id, 0x0003d800, 0x400), bgmaps: memory.watch(sim_id, 0x00020000, 0x20000), index: params.index, param_index: 0, generic_palette: params.generic_palette, params, scale: 1.0, } } fn show_form(&mut self, ui: &mut Ui) { let row_height = ui.spacing().interact_size.y; ui.vertical(|ui| { TableBuilder::new(ui) .column(Column::auto()) .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.index).range(0..32)); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Address"); }); row.col(|ui| { let address = 0x0003d800 + self.index * 32; let mut address_str = format!("{address:08x}"); ui.add_enabled( false, TextEdit::singleline(&mut address_str).horizontal_align(Align::Max), ); }); }); }); let mut data = { let worlds = self.worlds.borrow(); worlds.read(self.index) }; let mut world = World::parse(&data); ui.section("Properties", |ui| { TableBuilder::new(ui) .column(Column::remainder()) .column(Column::remainder()) .body(|mut body| { body.row(row_height, |mut row| { row.col(|ui| { ui.label("Map base"); }); row.col(|ui| { ui.add(NumberEdit::new(&mut world.header.base).range(0..16)); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("BG width"); }); row.col(|ui| { let widths = ["1", "2", "4", "8"]; ComboBox::from_id_salt("width") .selected_text(widths[world.header.scx as usize]) .width(ui.available_width()) .show_ui(ui, |ui| { for (value, label) in widths.into_iter().enumerate() { ui.selectable_value( &mut world.header.scx, value as u32, label, ); } }); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("BG height"); }); row.col(|ui| { let heights = ["1", "2", "4", "8"]; ComboBox::from_id_salt("height") .selected_text(heights[world.header.scy as usize]) .width(ui.available_width()) .show_ui(ui, |ui| { for (value, label) in heights.into_iter().enumerate() { ui.selectable_value( &mut world.header.scy, value as u32, label, ); } }); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Dest X"); }); row.col(|ui| { ui.add(NumberEdit::new(&mut world.dst_x).range(-512..512)); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Dest Y"); }); row.col(|ui| { ui.add(NumberEdit::new(&mut world.dst_y)); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Dest parallax"); }); row.col(|ui| { ui.add(NumberEdit::new(&mut world.dst_parallax).range(-512..512)); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Src X"); }); row.col(|ui| { ui.add(NumberEdit::new(&mut world.src_x).range(-4096..4096)); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Src Y"); }); row.col(|ui| { ui.add(NumberEdit::new(&mut world.src_y).range(-16384..16384)); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Src parallax"); }); row.col(|ui| { ui.add(NumberEdit::new(&mut world.src_parallax).range(-4096..4096)); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Width"); }); row.col(|ui| { ui.add(NumberEdit::new(&mut world.width).range(-4096..4096)); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Height"); }); row.col(|ui| { ui.add(NumberEdit::new(&mut world.height)); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Mode"); }); row.col(|ui| { ComboBox::from_id_salt("mode") .selected_text(world.header.mode.to_string()) .width(ui.available_width()) .show_ui(ui, |ui| { for mode in WorldMode::values() { ui.selectable_value( &mut world.header.mode, mode, mode.to_string(), ); } }); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Params"); }); row.col(|ui| { let address = 0x00020000 + world.param_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("Overplane"); }); row.col(|ui| { ui.add(NumberEdit::new(&mut world.overplane).range(0..65536)); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.add(Checkbox::new(&mut world.header.lon, "Left")); }); row.col(|ui| { ui.add(Checkbox::new(&mut world.header.ron, "Right")); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.add(Checkbox::new(&mut world.header.end, "End")); }); row.col(|ui| { ui.add(Checkbox::new(&mut world.header.over, "Overplane")); }); }); }); }); if world.update(&mut data) { let address = 0x0003d800 + self.index * 32; self.memory.write(self.sim_id, address as u32, &data); } 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| { let max = world.height.max(8) as usize; ui.add(NumberEdit::new(&mut self.param_index).range(0..max)); }); }); let base = (world.param_base + self.param_index * 2) & 0x1ffff; 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)); }); }); param.save(&self.memory, self.sim_id, base); }); }); } else if world.header.mode == WorldMode::Affine { ui.section("Affine", |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| { let max = world.height.max(1) as usize; ui.add(NumberEdit::new(&mut self.param_index).range(0..max)); }); }); let base = (world.param_base + self.param_index * 8) & 0x1ffff; let mut param = AffineParam::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("Src X"); }); row.col(|ui| { ui.add(NumberEdit::new(&mut param.src_x).precision(3)); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Src Y"); }); row.col(|ui| { ui.add(NumberEdit::new(&mut param.src_y).precision(3)); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Src parallax"); }); row.col(|ui| { ui.add(NumberEdit::new(&mut param.src_parallax)); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Delta X"); }); row.col(|ui| { ui.add(NumberEdit::new(&mut param.dx).precision(9)); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Delta Y"); }); row.col(|ui| { ui.add(NumberEdit::new(&mut param.dy).precision(9)); }); }); param.save(&self.memory, self.sim_id, base); }); }); } else { self.param_index = 0; } ui.section("Display", |ui| { ui.horizontal(|ui| { ui.label("Scale"); ui.spacing_mut().slider_width = ui.available_width(); let slider = Slider::new(&mut self.scale, 1.0..=10.0) .step_by(1.0) .show_value(false); ui.add(slider); }); ui.checkbox(&mut self.generic_palette, "Generic palette"); }); }); self.params.write(WorldParams { index: self.index, generic_palette: self.generic_palette, ..*self.params }); } fn show_world(&mut self, ui: &mut Ui) { let image = Image::new("vip://world") .fit_to_original_size(self.scale) .texture_options(TextureOptions::NEAREST); ui.add(image); } } impl AppWindow for WorldWindow { fn viewport_id(&self) -> ViewportId { ViewportId::from_hash_of(format!("world-{}", self.sim_id)) } fn sim_id(&self) -> SimId { self.sim_id } fn initial_viewport(&self) -> ViewportBuilder { ViewportBuilder::default() .with_title(format!("Worlds ({})", self.sim_id)) .with_inner_size((640.0, 500.0)) } fn on_init(&mut self, ctx: &Context, _render_state: &egui_wgpu::RenderState) { ctx.add_texture_loader(self.loader.clone()); } fn show(&mut self, ctx: &Context) { CentralPanel::default().show(ctx, |ui| { ui.horizontal_top(|ui| { StripBuilder::new(ui) .size(Size::relative(0.3).at_most(200.0)) .size(Size::remainder()) .horizontal(|mut strip| { strip.cell(|ui| { ScrollArea::vertical().show(ui, |ui| self.show_form(ui)); }); strip.cell(|ui| { ScrollArea::both().show(ui, |ui| self.show_world(ui)); }); }); }); }); } } #[derive(Clone, PartialEq, Eq)] struct WorldParams { index: usize, generic_palette: bool, left_color: Color32, right_color: Color32, } struct WorldRenderer { chardata: MemoryView, bgmaps: MemoryView, objects: MemoryView, worlds: MemoryView, brightness: MemoryView, palettes: MemoryView, scr: MemoryView, // an object world could update the same pixel more than once, // so we can't add the left/right eye color to the output buffer directly. buffer: Box<[[u8; 2]; 384 * 224]>, } impl WorldRenderer { pub fn new(sim_id: SimId, memory: &MemoryClient) -> Self { Self { chardata: memory.watch(sim_id, 0x00078000, 0x8000), bgmaps: memory.watch(sim_id, 0x00020000, 0x20000), objects: memory.watch(sim_id, 0x0003e000, 0x2000), worlds: memory.watch(sim_id, 0x0003d800, 0x400), brightness: memory.watch(sim_id, 0x0005f824, 8), palettes: memory.watch(sim_id, 0x0005f860, 16), scr: memory.watch(sim_id, 0x0005f848, 8), buffer: vec![[0, 0]; 384 * 224] .into_boxed_slice() .try_into() .unwrap(), } } fn render_object_world(&mut self, group: usize, params: &WorldParams, image: &mut ImageBuffer) { for cell in self.buffer.iter_mut() { *cell = [0, 0]; } let palettes = { let palettes = self.palettes.borrow().read::<[u8; 8]>(1); [ utils::parse_palette(palettes[0]), utils::parse_palette(palettes[2]), utils::parse_palette(palettes[4]), utils::parse_palette(palettes[6]), ] }; let chardata = self.chardata.borrow(); let objects = self.objects.borrow(); let (first_range, second_range) = { let scr = self.scr.borrow(); let start = if group == 0 { 0 } else { scr.read::(group - 1).wrapping_add(1) as usize & 0x03ff }; let end = scr.read::(group).wrapping_add(1) as usize & 0x03ff; if start > end { ((start, 1024 - start), (end != 0).then_some((0, end))) } else { ((start, end - start), None) } }; // Fill the buffer for object in std::iter::once(first_range) .chain(second_range) .flat_map(|(start, count)| objects.range(start, count)) .rev() { let obj = Object::parse(object); if !obj.lon && !obj.ron { continue; } let char = chardata.read::<[u16; 8]>(obj.data.char_index); let palette = &palettes[obj.data.palette_index]; for row in 0..8 { let y = obj.y + row as i16; if !(0..224).contains(&y) { continue; } for (col, pixel) in utils::read_char_row(&char, obj.data.hflip, obj.data.vflip, row).enumerate() { if pixel == 0 { // transparent continue; } let lx = obj.x + col as i16 - obj.parallax; if obj.lon && (0..384).contains(&lx) { let index = (y as usize) * 384 + lx as usize; self.buffer[index][0] = palette[pixel as usize]; } let rx = obj.x + col as i16 + obj.parallax; if obj.ron & (0..384).contains(&rx) { let index = (y as usize) * 384 + rx as usize; self.buffer[index][1] = palette[pixel as usize]; } } } } let colors = if params.generic_palette { [ utils::generic_palette(params.left_color), utils::generic_palette(params.right_color), ] } else { let brts = self.brightness.borrow().read::<[u8; 8]>(0); let shades = utils::parse_shades(&brts); [ shades.map(|s| shade(s, params.left_color)), shades.map(|s| shade(s, params.right_color)), ] }; for (dst, shades) in image.pixels.iter_mut().zip(self.buffer.iter()) { let left = colors[0][shades[0] as usize]; let right = colors[1][shades[1] as usize]; *dst = Color32::from_rgb( left.r() + right.r(), left.g() + right.g(), left.b() + right.b(), ) } } fn render_world(&mut self, world: World, params: &WorldParams, image: &mut ImageBuffer) { image.clear(); let width = if world.header.mode == WorldMode::Affine { world.width & 0x03ff } else { world.width }; let height = if world.header.mode == WorldMode::Affine { world.height.max(8) } else { world.height }; let dx1 = world.dst_x; let dx2 = dx1 + width; if dx1 - world.dst_parallax > 384 || dx2 + world.dst_parallax < 0 { return; } let dy1 = world.dst_y; let dy2 = dy1 + height; if dy1 > 224 || dy2 < 0 { return; } let colors = if params.generic_palette { [ utils::generic_palette(params.left_color), utils::generic_palette(params.right_color), ] } else { let brts = self.brightness.borrow().read::<[u8; 8]>(0); let shades = utils::parse_shades(&brts); [ shades.map(|s| shade(s, params.left_color)), shades.map(|s| shade(s, params.right_color)), ] }; let palettes = { let palettes = self.palettes.borrow().read::<[u8; 8]>(0); [ utils::parse_palette(palettes[0]), utils::parse_palette(palettes[2]), utils::parse_palette(palettes[4]), utils::parse_palette(palettes[6]), ] }; 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; } for x in 0..width { let dx = x + world.dst_x - world.dst_parallax; 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); let shade = palettes[cell.palette_index][pixel as usize]; image.add((dx as usize, dy as usize), colors[0][shade as usize]); } let dx = x + world.dst_x + world.dst_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[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); let shade = palettes[cell.palette_index][pixel as usize]; image.add((dx as usize, dy as usize), colors[1][shade as usize]); } } } } } impl ImageRenderer<1> for WorldRenderer { type Params = WorldParams; fn sizes(&self) -> [[usize; 2]; 1] { [[384, 224]] } fn render(&mut self, params: &Self::Params, images: &mut [ImageBuffer; 1]) { let image = &mut images[0]; let worlds = self.worlds.borrow(); let header = WorldHeader::parse(worlds.read(params.index * 16)); if header.end || (!header.lon && !header.ron) { image.clear(); return; } if header.mode == WorldMode::Object { let mut group = 3usize; for world in params.index + 1..32 { let header = WorldHeader::parse(worlds.read(world * 16)); if header.mode == WorldMode::Object && (header.lon || header.ron) { group = group.checked_sub(1).unwrap_or(3); } } drop(worlds); self.render_object_world(group, params, image); } else { let world = World::parse(&worlds.read(params.index)); drop(worlds); self.render_world(world, params, image); } } } struct World { header: WorldHeader, dst_x: i16, dst_parallax: i16, dst_y: i16, src_x: i16, src_parallax: i16, src_y: i16, width: i16, height: i16, param_base: usize, overplane: usize, } impl World { pub fn parse(data: &[u16; 16]) -> Self { Self { header: WorldHeader::parse(data[0]), dst_x: (data[1] as i16) << 6 >> 6, dst_parallax: (data[2] as i16) << 6 >> 6, dst_y: data[3] as i16, src_x: (data[4] as i16) << 3 >> 3, src_parallax: (data[5] as i16) << 1 >> 1, src_y: (data[6] as i16) << 3 >> 3, width: 1 + ((data[7] as i16) << 3 >> 3), height: 1 + data[8] as i16, param_base: data[9] as usize, overplane: data[10] as usize, } } fn update(&self, source: &mut [u16; 16]) -> bool { let mut changed = self.header.update(&mut source[0]); let new_dst_x = (self.dst_x as u16 & 0x03ff) | (source[1] & 0xfc00); changed |= source[1] != new_dst_x; source[1] = new_dst_x; let new_dst_parallax = (self.dst_parallax as u16 & 0x03ff) | (source[2] & 0xfc00); changed |= source[2] != new_dst_parallax; source[2] = new_dst_parallax; let new_dst_y = self.dst_y as u16; changed |= source[3] != new_dst_y; source[3] = new_dst_y; let new_src_x = (self.src_x as u16 & 0x1fff) | (source[4] & 0xe000); changed |= source[4] != new_src_x; source[4] = new_src_x; let new_src_parallax = (self.src_parallax as u16 & 0x7fff) | (source[4] & 0x8000); changed |= source[5] != new_src_parallax; source[5] = new_src_parallax; let new_src_y = (self.src_y as u16 & 0x1fff) | (source[6] & 0xe000); changed |= source[6] != new_src_y; source[6] = new_src_y; let new_width = ((self.width - 1) as u16 & 0x1fff) | (source[7] & 0xe000); changed |= source[7] != new_width; source[7] = new_width; let new_height = (self.height - 1) as u16; changed |= source[8] != new_height; source[8] = new_height; let new_param_base = self.param_base as u16; changed |= source[9] != new_param_base; source[9] = new_param_base; let new_overplane = self.overplane as u16; changed |= source[10] != new_overplane; source[10] = new_overplane; changed } fn source_cell(&self, sx: i16, sy: i16) -> usize { if self.header.over { let bg_width = 1 << self.header.scx << 9; let bg_height = 1 << self.header.scy << 9; if !(0..bg_width).contains(&sx) || !(0..bg_height).contains(&sy) { return self.overplane; } } let scx = 1 << self.header.scx.min(3 - self.header.scy); let scy = 1 << self.header.scy; let map_x = ((sx >> 9) & (scx - 1)) as usize; let map_y = ((sy >> 9) & (scy - 1)) as usize; let map_index = self.header.base + (map_y * scx as usize) + map_x; let cell_x = (sx >> 3) as usize & 0x3f; let cell_y = (sy >> 3) as usize & 0x3f; let cell_index = (cell_y * 64) + cell_x; (map_index << 12) + cell_index } } struct WorldHeader { lon: bool, ron: bool, mode: WorldMode, scx: u32, scy: u32, over: bool, end: bool, base: usize, } impl WorldHeader { fn parse(data: u16) -> Self { let lon = data & 0x8000 != 0; let ron = data & 0x4000 != 0; let mode = WorldMode::from_u16((data >> 12) & 0x3).unwrap(); let scx = (data >> 10) as u32 & 0x03; let scy = (data >> 10) as u32 & 0x03; let over = data & 0x0080 != 0; let end = data & 0x0040 != 0; let base = (data & 0x000f) as usize; Self { lon, ron, mode, scx, scy, over, end, base, } } fn update(&self, source: &mut u16) -> bool { let new_value = (*source & 0x0030) | if self.lon { 0x8000 } else { 0x0000 } | if self.ron { 0x4000 } else { 0x0000 } | self.mode.to_u16().unwrap() << 12 | ((self.scx as u16) << 10) | ((self.scy as u16) << 8) | if self.over { 0x0080 } else { 0x0000 } | if self.end { 0x0040 } else { 0x0000 } | (self.base as u16 & 0x000f); let changed = *source != new_value; *source = new_value; changed } } #[derive(Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)] enum WorldMode { Normal = 0, HBias = 1, Affine = 2, Object = 3, } impl WorldMode { fn values() -> [Self; 4] { [Self::Normal, Self::HBias, Self::Affine, Self::Object] } } impl Display for WorldMode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { Self::Normal => "Normal", Self::HBias => "H-bias", Self::Affine => "Affine", Self::Object => "Object", }) } } struct CellCache<'a> { bgmaps: &'a MemoryRef<'a>, index: usize, cell: CellData, } impl<'a> CellCache<'a> { fn new(bgmaps: &'a MemoryRef<'a>) -> Self { Self { bgmaps, index: 0x10000, cell: CellData::parse(0), } } fn get(&mut self, index: usize) -> &CellData { if self.index != index { let data = self.bgmaps.read(index); self.cell = CellData::parse(data); self.index = index; } &self.cell } } struct CharCache<'a> { chardata: &'a MemoryRef<'a>, index: usize, char: [u16; 8], } impl<'a> CharCache<'a> { fn new(chardata: &'a MemoryRef<'a>) -> Self { Self { chardata, index: 2048, char: [0; 8], } } fn get(&mut self, index: usize) -> &[u16; 8] { if self.index != index { self.char = self.chardata.read(index); self.index = index; } &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::Normal => { let sx = x + self.world.src_x - self.world.src_parallax; let sy = y + self.world.src_y; (sx, sy) } 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::Affine(affine) => { let sx = affine_coord(affine.src_x, x, affine.dx, affine.src_parallax.min(0)); let sy = affine_coord(affine.src_y, x, affine.dy, affine.src_parallax.min(0)); (sx, sy) } } } fn right(&mut self, x: i16, y: i16) -> (i16, i16) { self.update_param(y); match &self.param { SourceParam::Normal => { let sx = x + self.world.src_x + self.world.src_parallax; let sy = y + self.world.src_y; (sx, sy) } 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::Affine(affine) => { let sx = affine_coord(affine.src_x, x, affine.dx, affine.src_parallax.max(0)); let sy = affine_coord(affine.src_y, x, affine.dy, affine.src_parallax.max(0)); (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)); } if self.world.header.mode == WorldMode::Affine { let base = self.world.param_base + (8 * y as usize); self.param = SourceParam::Affine(AffineParam::load(self.params, base)); } self.y = y; } } enum SourceParam { Normal, HBias(HBiasParam), Affine(AffineParam), } struct HBiasParam { left: i16, right: i16, data: [u16; 2], } impl HBiasParam { fn load(params: &MemoryRef, index: usize) -> Self { let data = [params.read::(index), params.read::(index | 1)]; let left = (data[0] as i16) << 3 >> 3; let right = (data[1] as i16) << 3 >> 3; Self { left, right, data } } fn save(&self, memory: &MemoryClient, sim: SimId, index: usize) { let new_left = (self.left as u16 & 0x1fff) | (self.data[0] & 0xe000); if new_left != self.data[0] { let address = 0x00020000 + (index * 2); memory.write(sim, address as u32, &new_left); } let new_right = (self.right as u16 & 0x1fff) | (self.data[1] & 0xe000); if new_right != self.data[1] { let address = 0x00020000 + ((index | 1) * 2); memory.write(sim, address as u32, &new_right); } } } struct AffineParam { src_x: FixedI32, src_parallax: i16, src_y: FixedI32, dx: FixedI32, dy: FixedI32, data: [u16; 5], } impl AffineParam { fn load(params: &MemoryRef, index: usize) -> Self { let data = [ params.read(index & 0xffff), params.read((index + 1) & 0xffff), params.read((index + 2) & 0xffff), params.read((index + 3) & 0xffff), params.read((index + 4) & 0xffff), ]; let src_x = FixedI32::from_bits(data[0] as i16 as i32); let src_parallax = data[1] as i16; let src_y = FixedI32::from_bits(data[2] as i16 as i32); let dx = FixedI32::from_bits(data[3] as i16 as i32); let dy = FixedI32::from_bits(data[4] as i16 as i32); AffineParam { src_x, src_parallax, src_y, dx, dy, data, } } fn save(&self, memory: &MemoryClient, sim: SimId, index: usize) { let new_src_x = self.src_x.to_bits() as u16; if new_src_x != self.data[0] { let address = 0x00020000 + 2 * (index & 0xffff); memory.write(sim, address as u32, &new_src_x); } let new_src_parallax = self.src_parallax as u16; if new_src_parallax != self.data[1] { let address = 0x00020000 + 2 * ((index + 1) & 0xffff); memory.write(sim, address as u32, &new_src_parallax); } let new_src_y = self.src_y.to_bits() as u16; if new_src_y != self.data[2] { let address = 0x00020000 + 2 * ((index + 2) & 0xffff); memory.write(sim, address as u32, &new_src_y); } let new_dx = self.dx.to_bits() as u16; if new_dx != self.data[3] { let address = 0x00020000 + 2 * ((index + 3) & 0xffff); memory.write(sim, address as u32, &new_dx); } let new_dy = self.dy.to_bits() as u16; if new_dy != self.data[4] { let address = 0x00020000 + 2 * ((index + 4) & 0xffff); memory.write(sim, address as u32, &new_dy); } } } fn affine_coord(start: FixedI32, distance: i16, delta: FixedI32, parallax: i16) -> i16 { let start = FixedI32::::from_num(start); let distance = FixedI32::::from_num(distance); let parallax = FixedI32::::from_num(parallax); let coord = start + ((distance + parallax) * delta); coord.to_num::() as i16 }