lemur/src/window/vip/world.rs

1222 lines
45 KiB
Rust

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<ImageTextureLoader>,
memory: Arc<MemoryClient>,
worlds: MemoryView,
bgmaps: MemoryView,
index: usize,
param_index: usize,
generic_palette: bool,
show_extents: bool,
params: ImageParams<WorldParams>,
scale: f32,
}
impl WorldWindow {
pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>, 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,
show_extents: false,
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");
ui.checkbox(&mut self.show_extents, "Show extents");
});
});
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);
let res = ui.add(image);
if self.show_extents {
let world = {
let worlds = self.worlds.borrow();
let data = worlds.read(self.index);
World::parse(&data)
};
if world.header.mode == WorldMode::Object {
return;
}
let lx1 = (world.dst_x - world.dst_parallax) as f32 * self.scale;
let lx2 = lx1 + world.width as f32 * self.scale;
let rx1 = (world.dst_x + world.dst_parallax) as f32 * self.scale;
let rx2 = rx1 + world.width as f32 * self.scale;
let y1 = world.dst_y as f32 * self.scale;
let y2 = y1 + world.height as f32 * self.scale;
let left_color = self.params.left_color;
let right_color = self.params.right_color;
let both_color = Color32::from_rgb(
left_color.r() + right_color.r(),
left_color.g() + right_color.g(),
left_color.b() + right_color.b(),
);
let painter = ui.painter();
let draw_rect = |x1: f32, x2: f32, color: Color32| {
painter.line(
vec![
res.rect.min + (x1, y1).into(),
res.rect.min + (x2, y1).into(),
res.rect.min + (x2, y2).into(),
res.rect.min + (x1, y2).into(),
res.rect.min + (x1, y1).into(),
],
(2.0, color),
)
};
match (world.header.lon, world.header.ron) {
(false, false) => {}
(true, false) => {
draw_rect(lx1, lx2, left_color);
}
(false, true) => {
draw_rect(rx1, rx2, right_color);
}
(true, true) if world.dst_parallax == 0 => {
draw_rect(lx1, lx2, both_color);
}
(true, true) => {
draw_rect(lx1, lx2, left_color);
draw_rect(rx1, rx2, right_color);
let (x1, x2) = if world.dst_parallax < 0 {
(lx1, rx2)
} else {
(rx1, lx2)
};
painter.line_segment(
[
res.rect.min + (x1, y1).into(),
res.rect.min + (x2 + 1.0, y1).into(),
],
(2.0, both_color),
);
painter.line_segment(
[
res.rect.min + (x1, y2).into(),
res.rect.min + (x2 + 1.0, y2).into(),
],
(2.0, both_color),
);
}
}
}
}
}
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, 520.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::<u16>(group - 1).wrapping_add(1) as usize & 0x03ff
};
let end = scr.read::<u16>(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::<u16>(index), params.read::<u16>(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<U3>,
src_parallax: i16,
src_y: FixedI32<U3>,
dx: FixedI32<U9>,
dy: FixedI32<U9>,
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<U3>, distance: i16, delta: FixedI32<U9>, parallax: i16) -> i16 {
let start = FixedI32::<U9>::from_num(start);
let distance = FixedI32::<U9>::from_num(distance);
let parallax = FixedI32::<U9>::from_num(parallax);
let coord = start + ((distance + parallax) * delta);
coord.to_num::<i32>() as i16
}