View normal worlds
This commit is contained in:
parent
cfc08032e6
commit
2a4599756c
|
@ -40,7 +40,7 @@ impl BgMapWindow {
|
|||
sim_id,
|
||||
loader: Arc::new(loader),
|
||||
memory: memory.clone(),
|
||||
bgmaps: memory.watch(sim_id, 0x00020000, 0x1d800),
|
||||
bgmaps: memory.watch(sim_id, 0x00020000, 0x20000),
|
||||
cell_index: params.cell_index,
|
||||
generic_palette: params.generic_palette,
|
||||
params,
|
||||
|
@ -62,7 +62,7 @@ impl BgMapWindow {
|
|||
});
|
||||
row.col(|ui| {
|
||||
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 {
|
||||
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 {
|
||||
Self {
|
||||
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),
|
||||
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> {
|
||||
source: ImageSource<'a>,
|
||||
scale: f32,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use std::{fmt::Display, sync::Arc};
|
||||
|
||||
use egui::{
|
||||
CentralPanel, Checkbox, Color32, ComboBox, Context, Image, ScrollArea, Slider, TextureOptions,
|
||||
Ui, ViewportBuilder, ViewportId,
|
||||
Align, CentralPanel, Checkbox, Color32, ComboBox, Context, Image, ScrollArea, Slider, TextEdit,
|
||||
TextureOptions, Ui, ViewportBuilder, ViewportId,
|
||||
};
|
||||
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
|
||||
use num_derive::{FromPrimitive, ToPrimitive};
|
||||
|
@ -10,7 +10,7 @@ use num_traits::FromPrimitive;
|
|||
|
||||
use crate::{
|
||||
emulator::SimId,
|
||||
memory::{MemoryClient, MemoryView},
|
||||
memory::{MemoryClient, MemoryRef, MemoryView},
|
||||
vram::{VramImage, VramParams, VramProcessor, VramRenderer, VramTextureLoader},
|
||||
window::{
|
||||
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 {
|
||||
sim_id: SimId,
|
||||
|
@ -67,6 +67,19 @@ impl WorldWindow {
|
|||
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 worlds = self.worlds.borrow();
|
||||
|
@ -78,6 +91,118 @@ impl WorldWindow {
|
|||
.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");
|
||||
|
@ -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| {
|
||||
row.col(|ui| {
|
||||
ui.add(Checkbox::new(&mut world.header.lon, "Left"));
|
||||
|
@ -154,7 +301,7 @@ impl AppWindow for WorldWindow {
|
|||
fn initial_viewport(&self) -> ViewportBuilder {
|
||||
ViewportBuilder::default()
|
||||
.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) {
|
||||
|
@ -190,6 +337,7 @@ struct WorldParams {
|
|||
|
||||
struct WorldRenderer {
|
||||
chardata: MemoryView,
|
||||
bgmaps: MemoryView,
|
||||
objects: MemoryView,
|
||||
worlds: MemoryView,
|
||||
brightness: MemoryView,
|
||||
|
@ -204,6 +352,7 @@ 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),
|
||||
|
@ -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 {
|
||||
|
@ -342,28 +571,77 @@ impl VramRenderer<1> for WorldRenderer {
|
|||
}
|
||||
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 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 {
|
||||
|
@ -371,14 +649,20 @@ impl WorldHeader {
|
|||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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