View normal worlds

This commit is contained in:
Simon Gellis 2025-02-17 00:20:12 -05:00
parent cfc08032e6
commit 2a4599756c
3 changed files with 347 additions and 8 deletions

View File

@ -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),
} }

View File

@ -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,

View File

@ -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
}
}