VIP inspection tooling #4
			
				
			
		
		
		
	| 
						 | 
					@ -40,7 +40,7 @@ impl BgMapWindow {
 | 
				
			||||||
            sim_id,
 | 
					            sim_id,
 | 
				
			||||||
            loader: Arc::new(loader),
 | 
					            loader: Arc::new(loader),
 | 
				
			||||||
            memory: memory.clone(),
 | 
					            memory: memory.clone(),
 | 
				
			||||||
            bgmaps: memory.watch(sim_id, 0x00020000, 0x1d800),
 | 
					            bgmaps: memory.watch(sim_id, 0x00020000, 0x20000),
 | 
				
			||||||
            cell_index: params.cell_index,
 | 
					            cell_index: params.cell_index,
 | 
				
			||||||
            generic_palette: params.generic_palette,
 | 
					            generic_palette: params.generic_palette,
 | 
				
			||||||
            params,
 | 
					            params,
 | 
				
			||||||
| 
						 | 
					@ -62,7 +62,7 @@ impl BgMapWindow {
 | 
				
			||||||
                        });
 | 
					                        });
 | 
				
			||||||
                        row.col(|ui| {
 | 
					                        row.col(|ui| {
 | 
				
			||||||
                            let mut bgmap_index = self.cell_index / 4096;
 | 
					                            let mut bgmap_index = self.cell_index / 4096;
 | 
				
			||||||
                            ui.add(NumberEdit::new(&mut bgmap_index).range(0..14));
 | 
					                            ui.add(NumberEdit::new(&mut bgmap_index).range(0..16));
 | 
				
			||||||
                            if bgmap_index != self.cell_index / 4096 {
 | 
					                            if bgmap_index != self.cell_index / 4096 {
 | 
				
			||||||
                                self.cell_index = (bgmap_index * 4096) + (self.cell_index % 4096);
 | 
					                                self.cell_index = (bgmap_index * 4096) + (self.cell_index % 4096);
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
| 
						 | 
					@ -227,7 +227,7 @@ impl BgMapRenderer {
 | 
				
			||||||
    pub fn new(sim_id: SimId, memory: &MemoryClient) -> Self {
 | 
					    pub fn new(sim_id: SimId, memory: &MemoryClient) -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            chardata: memory.watch(sim_id, 0x00078000, 0x8000),
 | 
					            chardata: memory.watch(sim_id, 0x00078000, 0x8000),
 | 
				
			||||||
            bgmaps: memory.watch(sim_id, 0x00020000, 0x1d800),
 | 
					            bgmaps: memory.watch(sim_id, 0x00020000, 0x20000),
 | 
				
			||||||
            brightness: memory.watch(sim_id, 0x0005f824, 8),
 | 
					            brightness: memory.watch(sim_id, 0x0005f824, 8),
 | 
				
			||||||
            palettes: memory.watch(sim_id, 0x0005f860, 16),
 | 
					            palettes: memory.watch(sim_id, 0x0005f860, 16),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -134,6 +134,12 @@ pub fn read_char_row(
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn read_char_pixel(char: &[u16; 8], hflip: bool, vflip: bool, row: usize, col: usize) -> u8 {
 | 
				
			||||||
 | 
					    let pixels = if vflip { char[7 - row] } else { char[row] };
 | 
				
			||||||
 | 
					    let pixel = if hflip { 7 - col } else { col } << 1;
 | 
				
			||||||
 | 
					    ((pixels >> pixel) & 0x3) as u8
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct CharacterGrid<'a> {
 | 
					pub struct CharacterGrid<'a> {
 | 
				
			||||||
    source: ImageSource<'a>,
 | 
					    source: ImageSource<'a>,
 | 
				
			||||||
    scale: f32,
 | 
					    scale: f32,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,8 @@
 | 
				
			||||||
use std::{fmt::Display, sync::Arc};
 | 
					use std::{fmt::Display, sync::Arc};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use egui::{
 | 
					use egui::{
 | 
				
			||||||
    CentralPanel, Checkbox, Color32, ComboBox, Context, Image, ScrollArea, Slider, TextureOptions,
 | 
					    Align, CentralPanel, Checkbox, Color32, ComboBox, Context, Image, ScrollArea, Slider, TextEdit,
 | 
				
			||||||
    Ui, ViewportBuilder, ViewportId,
 | 
					    TextureOptions, Ui, ViewportBuilder, ViewportId,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
 | 
					use egui_extras::{Column, Size, StripBuilder, TableBuilder};
 | 
				
			||||||
use num_derive::{FromPrimitive, ToPrimitive};
 | 
					use num_derive::{FromPrimitive, ToPrimitive};
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,7 @@ use num_traits::FromPrimitive;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    emulator::SimId,
 | 
					    emulator::SimId,
 | 
				
			||||||
    memory::{MemoryClient, MemoryView},
 | 
					    memory::{MemoryClient, MemoryRef, MemoryView},
 | 
				
			||||||
    vram::{VramImage, VramParams, VramProcessor, VramRenderer, VramTextureLoader},
 | 
					    vram::{VramImage, VramParams, VramProcessor, VramRenderer, VramTextureLoader},
 | 
				
			||||||
    window::{
 | 
					    window::{
 | 
				
			||||||
        utils::{NumberEdit, UiExt as _},
 | 
					        utils::{NumberEdit, UiExt as _},
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,7 @@ use crate::{
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::utils::{self, shade, Object};
 | 
					use super::utils::{self, shade, CellData, Object};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct WorldWindow {
 | 
					pub struct WorldWindow {
 | 
				
			||||||
    sim_id: SimId,
 | 
					    sim_id: SimId,
 | 
				
			||||||
| 
						 | 
					@ -67,6 +67,19 @@ impl WorldWindow {
 | 
				
			||||||
                            ui.add(NumberEdit::new(&mut self.index).range(0..32));
 | 
					                            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 = 0x3d800 + self.index * 32;
 | 
				
			||||||
 | 
					                            let mut address_str = format!("{address:08x}");
 | 
				
			||||||
 | 
					                            ui.add_enabled(
 | 
				
			||||||
 | 
					                                false,
 | 
				
			||||||
 | 
					                                TextEdit::singleline(&mut address_str).horizontal_align(Align::Max),
 | 
				
			||||||
 | 
					                            );
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            let data = {
 | 
					            let data = {
 | 
				
			||||||
                let worlds = self.worlds.borrow();
 | 
					                let worlds = self.worlds.borrow();
 | 
				
			||||||
| 
						 | 
					@ -78,6 +91,118 @@ impl WorldWindow {
 | 
				
			||||||
                    .column(Column::remainder())
 | 
					                    .column(Column::remainder())
 | 
				
			||||||
                    .column(Column::remainder())
 | 
					                    .column(Column::remainder())
 | 
				
			||||||
                    .body(|mut body| {
 | 
					                    .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| {
 | 
					                        body.row(row_height, |mut row| {
 | 
				
			||||||
                            row.col(|ui| {
 | 
					                            row.col(|ui| {
 | 
				
			||||||
                                ui.label("Mode");
 | 
					                                ui.label("Mode");
 | 
				
			||||||
| 
						 | 
					@ -97,6 +222,28 @@ impl WorldWindow {
 | 
				
			||||||
                                    });
 | 
					                                    });
 | 
				
			||||||
                            });
 | 
					                            });
 | 
				
			||||||
                        });
 | 
					                        });
 | 
				
			||||||
 | 
					                        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| {
 | 
					                        body.row(row_height, |mut row| {
 | 
				
			||||||
                            row.col(|ui| {
 | 
					                            row.col(|ui| {
 | 
				
			||||||
                                ui.add(Checkbox::new(&mut world.header.lon, "Left"));
 | 
					                                ui.add(Checkbox::new(&mut world.header.lon, "Left"));
 | 
				
			||||||
| 
						 | 
					@ -154,7 +301,7 @@ impl AppWindow for WorldWindow {
 | 
				
			||||||
    fn initial_viewport(&self) -> ViewportBuilder {
 | 
					    fn initial_viewport(&self) -> ViewportBuilder {
 | 
				
			||||||
        ViewportBuilder::default()
 | 
					        ViewportBuilder::default()
 | 
				
			||||||
            .with_title(format!("Worlds ({})", self.sim_id))
 | 
					            .with_title(format!("Worlds ({})", self.sim_id))
 | 
				
			||||||
            .with_inner_size((640.0, 480.0))
 | 
					            .with_inner_size((640.0, 500.0))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn on_init(&mut self, ctx: &Context, _render_state: &egui_wgpu::RenderState) {
 | 
					    fn on_init(&mut self, ctx: &Context, _render_state: &egui_wgpu::RenderState) {
 | 
				
			||||||
| 
						 | 
					@ -190,6 +337,7 @@ struct WorldParams {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct WorldRenderer {
 | 
					struct WorldRenderer {
 | 
				
			||||||
    chardata: MemoryView,
 | 
					    chardata: MemoryView,
 | 
				
			||||||
 | 
					    bgmaps: MemoryView,
 | 
				
			||||||
    objects: MemoryView,
 | 
					    objects: MemoryView,
 | 
				
			||||||
    worlds: MemoryView,
 | 
					    worlds: MemoryView,
 | 
				
			||||||
    brightness: MemoryView,
 | 
					    brightness: MemoryView,
 | 
				
			||||||
| 
						 | 
					@ -204,6 +352,7 @@ impl WorldRenderer {
 | 
				
			||||||
    pub fn new(sim_id: SimId, memory: &MemoryClient) -> Self {
 | 
					    pub fn new(sim_id: SimId, memory: &MemoryClient) -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            chardata: memory.watch(sim_id, 0x00078000, 0x8000),
 | 
					            chardata: memory.watch(sim_id, 0x00078000, 0x8000),
 | 
				
			||||||
 | 
					            bgmaps: memory.watch(sim_id, 0x00020000, 0x20000),
 | 
				
			||||||
            objects: memory.watch(sim_id, 0x0003e000, 0x2000),
 | 
					            objects: memory.watch(sim_id, 0x0003e000, 0x2000),
 | 
				
			||||||
            worlds: memory.watch(sim_id, 0x0003d800, 0x400),
 | 
					            worlds: memory.watch(sim_id, 0x0003d800, 0x400),
 | 
				
			||||||
            brightness: memory.watch(sim_id, 0x0005f824, 8),
 | 
					            brightness: memory.watch(sim_id, 0x0005f824, 8),
 | 
				
			||||||
| 
						 | 
					@ -313,6 +462,86 @@ impl WorldRenderer {
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn render_world(&mut self, world: World, params: &WorldParams, image: &mut VramImage) {
 | 
				
			||||||
 | 
					        image.clear();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let height = if world.header.mode == WorldMode::Affine {
 | 
				
			||||||
 | 
					            world.height.max(8)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            world.height
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let dx1 = world.dst_x;
 | 
				
			||||||
 | 
					        let dx2 = dx1 + world.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 mut chars = CharCache::new(self.chardata.borrow());
 | 
				
			||||||
 | 
					        let mut cells = CellCache::new(self.bgmaps.borrow());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                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]);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl VramRenderer<1> for WorldRenderer {
 | 
					impl VramRenderer<1> for WorldRenderer {
 | 
				
			||||||
| 
						 | 
					@ -342,28 +571,77 @@ impl VramRenderer<1> for WorldRenderer {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            drop(worlds);
 | 
					            drop(worlds);
 | 
				
			||||||
            self.render_object_world(group, params, image);
 | 
					            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 {
 | 
					struct World {
 | 
				
			||||||
    header: WorldHeader,
 | 
					    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 {
 | 
					impl World {
 | 
				
			||||||
    pub fn parse(data: &[u16; 16]) -> Self {
 | 
					    pub fn parse(data: &[u16; 16]) -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            header: WorldHeader::parse(data[0]),
 | 
					            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 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 {
 | 
					struct WorldHeader {
 | 
				
			||||||
    lon: bool,
 | 
					    lon: bool,
 | 
				
			||||||
    ron: bool,
 | 
					    ron: bool,
 | 
				
			||||||
    mode: WorldMode,
 | 
					    mode: WorldMode,
 | 
				
			||||||
 | 
					    scx: u32,
 | 
				
			||||||
 | 
					    scy: u32,
 | 
				
			||||||
    over: bool,
 | 
					    over: bool,
 | 
				
			||||||
    end: bool,
 | 
					    end: bool,
 | 
				
			||||||
 | 
					    base: usize,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl WorldHeader {
 | 
					impl WorldHeader {
 | 
				
			||||||
| 
						 | 
					@ -371,14 +649,20 @@ impl WorldHeader {
 | 
				
			||||||
        let lon = data & 0x8000 != 0;
 | 
					        let lon = data & 0x8000 != 0;
 | 
				
			||||||
        let ron = data & 0x4000 != 0;
 | 
					        let ron = data & 0x4000 != 0;
 | 
				
			||||||
        let mode = WorldMode::from_u16((data >> 12) & 0x3).unwrap();
 | 
					        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 over = data & 0x0080 != 0;
 | 
				
			||||||
        let end = data & 0x0040 != 0;
 | 
					        let end = data & 0x0040 != 0;
 | 
				
			||||||
 | 
					        let base = (data & 0x000f) as usize;
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            lon,
 | 
					            lon,
 | 
				
			||||||
            ron,
 | 
					            ron,
 | 
				
			||||||
            mode,
 | 
					            mode,
 | 
				
			||||||
 | 
					            scx,
 | 
				
			||||||
 | 
					            scy,
 | 
				
			||||||
            over,
 | 
					            over,
 | 
				
			||||||
            end,
 | 
					            end,
 | 
				
			||||||
 | 
					            base,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -407,3 +691,52 @@ impl Display for WorldMode {
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct CellCache<'a> {
 | 
				
			||||||
 | 
					    bgmaps: MemoryRef<'a>,
 | 
				
			||||||
 | 
					    index: usize,
 | 
				
			||||||
 | 
					    cell: CellData,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<'a> CellCache<'a> {
 | 
				
			||||||
 | 
					    fn new(bgmaps: 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: MemoryRef<'a>,
 | 
				
			||||||
 | 
					    index: usize,
 | 
				
			||||||
 | 
					    char: [u16; 8],
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<'a> CharCache<'a> {
 | 
				
			||||||
 | 
					    fn new(chardata: 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
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue