Implement world viewer with support for OBJ worlds
This commit is contained in:
		
							parent
							
								
									f7cf960b62
								
							
						
					
					
						commit
						cfc08032e6
					
				| 
						 | 
				
			
			@ -23,7 +23,7 @@ use crate::{
 | 
			
		|||
    vram::VramProcessor,
 | 
			
		||||
    window::{
 | 
			
		||||
        AboutWindow, AppWindow, BgMapWindow, CharacterDataWindow, GameWindow, GdbServerWindow,
 | 
			
		||||
        InputWindow, ObjectWindow,
 | 
			
		||||
        InputWindow, ObjectWindow, WorldWindow,
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -225,6 +225,10 @@ impl ApplicationHandler<UserEvent> for Application {
 | 
			
		|||
                let objects = ObjectWindow::new(sim_id, &self.memory, &mut self.vram);
 | 
			
		||||
                self.open(event_loop, Box::new(objects));
 | 
			
		||||
            }
 | 
			
		||||
            UserEvent::OpenWorlds(sim_id) => {
 | 
			
		||||
                let world = WorldWindow::new(sim_id, &self.memory, &mut self.vram);
 | 
			
		||||
                self.open(event_loop, Box::new(world));
 | 
			
		||||
            }
 | 
			
		||||
            UserEvent::OpenDebugger(sim_id) => {
 | 
			
		||||
                let debugger =
 | 
			
		||||
                    GdbServerWindow::new(sim_id, self.client.clone(), self.proxy.clone());
 | 
			
		||||
| 
						 | 
				
			
			@ -486,6 +490,7 @@ pub enum UserEvent {
 | 
			
		|||
    OpenCharacterData(SimId),
 | 
			
		||||
    OpenBgMap(SimId),
 | 
			
		||||
    OpenObjects(SimId),
 | 
			
		||||
    OpenWorlds(SimId),
 | 
			
		||||
    OpenDebugger(SimId),
 | 
			
		||||
    OpenInput,
 | 
			
		||||
    OpenPlayer2,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
use std::{
 | 
			
		||||
    collections::HashMap,
 | 
			
		||||
    fmt::Debug,
 | 
			
		||||
    iter::FusedIterator,
 | 
			
		||||
    sync::{atomic::AtomicU64, Arc, Mutex, RwLock, RwLockReadGuard, TryLockError, Weak},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -117,7 +118,6 @@ impl<const N: usize, T: MemoryValue> MemoryValue for [T; N] {
 | 
			
		|||
 | 
			
		||||
pub struct MemoryIter<'a, T> {
 | 
			
		||||
    bytes: &'a [u8],
 | 
			
		||||
    index: usize,
 | 
			
		||||
    _phantom: std::marker::PhantomData<T>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -125,7 +125,6 @@ impl<'a, T> MemoryIter<'a, T> {
 | 
			
		|||
    fn new(bytes: &'a [u8]) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            bytes,
 | 
			
		||||
            index: 0,
 | 
			
		||||
            _phantom: std::marker::PhantomData,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -136,15 +135,30 @@ impl<T: MemoryValue> Iterator for MemoryIter<'_, T> {
 | 
			
		|||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn next(&mut self) -> Option<Self::Item> {
 | 
			
		||||
        if self.index >= self.bytes.len() {
 | 
			
		||||
            return None;
 | 
			
		||||
        }
 | 
			
		||||
        let bytes = &self.bytes[self.index..self.index + std::mem::size_of::<T>()];
 | 
			
		||||
        self.index += std::mem::size_of::<T>();
 | 
			
		||||
        let (bytes, rest) = self.bytes.split_at_checked(std::mem::size_of::<T>())?;
 | 
			
		||||
        self.bytes = rest;
 | 
			
		||||
        Some(T::from_bytes(bytes))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn size_hint(&self) -> (usize, Option<usize>) {
 | 
			
		||||
        let size = self.bytes.len() / std::mem::size_of::<T>();
 | 
			
		||||
        (size, Some(size))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T: MemoryValue> DoubleEndedIterator for MemoryIter<'_, T> {
 | 
			
		||||
    fn next_back(&mut self) -> Option<Self::Item> {
 | 
			
		||||
        let mid = self.bytes.len().checked_sub(std::mem::size_of::<T>())?;
 | 
			
		||||
        // SAFETY: the checked_sub above is effectively a bounds check
 | 
			
		||||
        let (rest, bytes) = unsafe { self.bytes.split_at_unchecked(mid) };
 | 
			
		||||
        self.bytes = rest;
 | 
			
		||||
        Some(T::from_bytes(bytes))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T: MemoryValue> FusedIterator for MemoryIter<'_, T> {}
 | 
			
		||||
 | 
			
		||||
impl MemoryRef<'_> {
 | 
			
		||||
    pub fn read<T: MemoryValue>(&self, index: usize) -> T {
 | 
			
		||||
        let from = index * size_of::<T>();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										27
									
								
								src/vram.rs
								
								
								
								
							
							
						
						
									
										27
									
								
								src/vram.rs
								
								
								
								
							| 
						 | 
				
			
			@ -39,6 +39,7 @@ impl VramProcessor {
 | 
			
		|||
    pub fn add<const N: usize, R: VramRenderer<N> + 'static>(
 | 
			
		||||
        &self,
 | 
			
		||||
        renderer: R,
 | 
			
		||||
        params: R::Params,
 | 
			
		||||
    ) -> ([VramImageHandle; N], VramParams<R::Params>) {
 | 
			
		||||
        let states = renderer.sizes().map(VramRenderImageState::new);
 | 
			
		||||
        let handles = states.clone().map(|state| VramImageHandle {
 | 
			
		||||
| 
						 | 
				
			
			@ -48,7 +49,7 @@ impl VramProcessor {
 | 
			
		|||
        let images = renderer
 | 
			
		||||
            .sizes()
 | 
			
		||||
            .map(|[width, height]| VramImage::new(width, height));
 | 
			
		||||
        let sink = Arc::new(Mutex::new(R::Params::default()));
 | 
			
		||||
        let sink = Arc::new(Mutex::new(params.clone()));
 | 
			
		||||
        let _ = self.sender.send(Box::new(VramRendererWrapper {
 | 
			
		||||
            renderer,
 | 
			
		||||
            params: Arc::downgrade(&sink),
 | 
			
		||||
| 
						 | 
				
			
			@ -56,7 +57,7 @@ impl VramProcessor {
 | 
			
		|||
            states,
 | 
			
		||||
        }));
 | 
			
		||||
        let params = VramParams {
 | 
			
		||||
            value: R::Params::default(),
 | 
			
		||||
            value: params,
 | 
			
		||||
            sink,
 | 
			
		||||
        };
 | 
			
		||||
        (handles, params)
 | 
			
		||||
| 
						 | 
				
			
			@ -100,32 +101,32 @@ impl VramProcessorWorker {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
pub struct VramImage {
 | 
			
		||||
    size: [usize; 2],
 | 
			
		||||
    shades: Vec<Color32>,
 | 
			
		||||
    pub size: [usize; 2],
 | 
			
		||||
    pub pixels: Vec<Color32>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl VramImage {
 | 
			
		||||
    pub fn new(width: usize, height: usize) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            size: [width, height],
 | 
			
		||||
            shades: vec![Color32::BLACK; width * height],
 | 
			
		||||
            pixels: vec![Color32::BLACK; width * height],
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn clear(&mut self) {
 | 
			
		||||
        for shade in self.shades.iter_mut() {
 | 
			
		||||
            *shade = Color32::BLACK;
 | 
			
		||||
        for pixel in self.pixels.iter_mut() {
 | 
			
		||||
            *pixel = Color32::BLACK;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn write(&mut self, coords: (usize, usize), pixel: Color32) {
 | 
			
		||||
        self.shades[coords.1 * self.size[0] + coords.0] = pixel;
 | 
			
		||||
        self.pixels[coords.1 * self.size[0] + coords.0] = pixel;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn add(&mut self, coords: (usize, usize), pixel: Color32) {
 | 
			
		||||
        let index = coords.1 * self.size[0] + coords.0;
 | 
			
		||||
        let old = self.shades[index];
 | 
			
		||||
        self.shades[index] = Color32::from_rgb(
 | 
			
		||||
        let old = self.pixels[index];
 | 
			
		||||
        self.pixels[index] = Color32::from_rgb(
 | 
			
		||||
            old.r() + pixel.r(),
 | 
			
		||||
            old.g() + pixel.g(),
 | 
			
		||||
            old.b() + pixel.b(),
 | 
			
		||||
| 
						 | 
				
			
			@ -133,11 +134,11 @@ impl VramImage {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn changed(&self, image: &ColorImage) -> bool {
 | 
			
		||||
        image.pixels.iter().zip(&self.shades).any(|(a, b)| a != b)
 | 
			
		||||
        image.pixels.iter().zip(&self.pixels).any(|(a, b)| a != b)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn read(&self, image: &mut ColorImage) {
 | 
			
		||||
        image.pixels.copy_from_slice(&self.shades);
 | 
			
		||||
        image.pixels.copy_from_slice(&self.pixels);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -176,7 +177,7 @@ impl<T: Clone + Eq> VramParams<T> {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
pub trait VramRenderer<const N: usize>: Send {
 | 
			
		||||
    type Params: Clone + Default + Send;
 | 
			
		||||
    type Params: Clone + Send;
 | 
			
		||||
    fn sizes(&self) -> [[usize; 2]; N];
 | 
			
		||||
    fn render(&mut self, params: &Self::Params, images: &mut [VramImage; N]);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ use egui::{Context, ViewportBuilder, ViewportId};
 | 
			
		|||
pub use game::GameWindow;
 | 
			
		||||
pub use gdb::GdbServerWindow;
 | 
			
		||||
pub use input::InputWindow;
 | 
			
		||||
pub use vram::{BgMapWindow, CharacterDataWindow, ObjectWindow};
 | 
			
		||||
pub use vram::{BgMapWindow, CharacterDataWindow, ObjectWindow, WorldWindow};
 | 
			
		||||
use winit::event::KeyEvent;
 | 
			
		||||
 | 
			
		||||
use crate::emulator::SimId;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -150,6 +150,12 @@ impl GameWindow {
 | 
			
		|||
                    .unwrap();
 | 
			
		||||
                ui.close_menu();
 | 
			
		||||
            }
 | 
			
		||||
            if ui.button("Worlds").clicked() {
 | 
			
		||||
                self.proxy
 | 
			
		||||
                    .send_event(UserEvent::OpenWorlds(self.sim_id))
 | 
			
		||||
                    .unwrap();
 | 
			
		||||
                ui.close_menu();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        ui.menu_button("Help", |ui| {
 | 
			
		||||
            if ui.button("About").clicked() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,9 @@ mod bgmap;
 | 
			
		|||
mod chardata;
 | 
			
		||||
mod object;
 | 
			
		||||
mod utils;
 | 
			
		||||
mod world;
 | 
			
		||||
 | 
			
		||||
pub use bgmap::*;
 | 
			
		||||
pub use chardata::*;
 | 
			
		||||
pub use object::*;
 | 
			
		||||
pub use world::*;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,7 +33,7 @@ pub struct BgMapWindow {
 | 
			
		|||
impl BgMapWindow {
 | 
			
		||||
    pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>, vram: &mut VramProcessor) -> Self {
 | 
			
		||||
        let renderer = BgMapRenderer::new(sim_id, memory);
 | 
			
		||||
        let ([cell, bgmap], params) = vram.add(renderer);
 | 
			
		||||
        let ([cell, bgmap], params) = vram.add(renderer, BgMapParams::default());
 | 
			
		||||
        let loader =
 | 
			
		||||
            VramTextureLoader::new([("vram://cell".into(), cell), ("vram://bgmap".into(), bgmap)]);
 | 
			
		||||
        Self {
 | 
			
		||||
| 
						 | 
				
			
			@ -41,8 +41,8 @@ impl BgMapWindow {
 | 
			
		|||
            loader: Arc::new(loader),
 | 
			
		||||
            memory: memory.clone(),
 | 
			
		||||
            bgmaps: memory.watch(sim_id, 0x00020000, 0x1d800),
 | 
			
		||||
            cell_index: 0,
 | 
			
		||||
            generic_palette: false,
 | 
			
		||||
            cell_index: params.cell_index,
 | 
			
		||||
            generic_palette: params.generic_palette,
 | 
			
		||||
            params,
 | 
			
		||||
            scale: 1.0,
 | 
			
		||||
            show_grid: false,
 | 
			
		||||
| 
						 | 
				
			
			@ -243,7 +243,7 @@ impl BgMapRenderer {
 | 
			
		|||
        let colors = if generic_palette {
 | 
			
		||||
            [utils::generic_palette(Color32::RED); 4]
 | 
			
		||||
        } else {
 | 
			
		||||
            [0, 2, 4, 6].map(|i| utils::parse_palette(palettes.read(i), &brts, Color32::RED))
 | 
			
		||||
            [0, 2, 4, 6].map(|i| utils::palette_colors(palettes.read(i), &brts, Color32::RED))
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        for (i, cell) in bgmaps.range::<u16>(bgmap_index * 4096, 4096).enumerate() {
 | 
			
		||||
| 
						 | 
				
			
			@ -286,7 +286,7 @@ impl BgMapRenderer {
 | 
			
		|||
        let palette = if generic_palette {
 | 
			
		||||
            utils::generic_palette(Color32::RED)
 | 
			
		||||
        } else {
 | 
			
		||||
            utils::parse_palette(palettes.read(palette_index * 2), &brts, Color32::RED)
 | 
			
		||||
            utils::palette_colors(palettes.read(palette_index * 2), &brts, Color32::RED)
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        for row in 0..8 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -94,7 +94,7 @@ pub struct CharacterDataWindow {
 | 
			
		|||
impl CharacterDataWindow {
 | 
			
		||||
    pub fn new(sim_id: SimId, memory: &MemoryClient, vram: &mut VramProcessor) -> Self {
 | 
			
		||||
        let renderer = CharDataRenderer::new(sim_id, memory);
 | 
			
		||||
        let ([char, chardata], params) = vram.add(renderer);
 | 
			
		||||
        let ([char, chardata], params) = vram.add(renderer, CharDataParams::default());
 | 
			
		||||
        let loader = VramTextureLoader::new([
 | 
			
		||||
            ("vram://char".into(), char),
 | 
			
		||||
            ("vram://chardata".into(), chardata),
 | 
			
		||||
| 
						 | 
				
			
			@ -222,7 +222,7 @@ impl CharacterDataWindow {
 | 
			
		|||
        let palette = self.palettes.borrow().read(offset);
 | 
			
		||||
        let brightnesses = self.brightness.borrow();
 | 
			
		||||
        let brts = brightnesses.read(0);
 | 
			
		||||
        utils::parse_palette(palette, &brts, Color32::RED)
 | 
			
		||||
        utils::palette_colors(palette, &brts, Color32::RED)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn show_chardata(&mut self, ui: &mut Ui) {
 | 
			
		||||
| 
						 | 
				
			
			@ -351,6 +351,6 @@ impl CharDataRenderer {
 | 
			
		|||
        let palette = self.palettes.borrow().read(offset);
 | 
			
		||||
        let brightnesses = self.brightness.borrow();
 | 
			
		||||
        let brts = brightnesses.read(0);
 | 
			
		||||
        utils::parse_palette(palette, &brts, Color32::RED)
 | 
			
		||||
        utils::palette_colors(palette, &brts, Color32::RED)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,8 +31,14 @@ pub struct ObjectWindow {
 | 
			
		|||
 | 
			
		||||
impl ObjectWindow {
 | 
			
		||||
    pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>, vram: &mut VramProcessor) -> Self {
 | 
			
		||||
        let initial_params = ObjectParams {
 | 
			
		||||
            index: 0,
 | 
			
		||||
            generic_palette: false,
 | 
			
		||||
            left_color: Color32::from_rgb(0xff, 0x00, 0x00),
 | 
			
		||||
            right_color: Color32::from_rgb(0x00, 0xc6, 0xf0),
 | 
			
		||||
        };
 | 
			
		||||
        let renderer = ObjectRenderer::new(sim_id, memory);
 | 
			
		||||
        let ([zoom, full], params) = vram.add(renderer);
 | 
			
		||||
        let ([zoom, full], params) = vram.add(renderer, initial_params);
 | 
			
		||||
        let loader =
 | 
			
		||||
            VramTextureLoader::new([("vram://zoom".into(), zoom), ("vram://full".into(), full)]);
 | 
			
		||||
        Self {
 | 
			
		||||
| 
						 | 
				
			
			@ -40,8 +46,8 @@ impl ObjectWindow {
 | 
			
		|||
            loader: Arc::new(loader),
 | 
			
		||||
            memory: memory.clone(),
 | 
			
		||||
            objects: memory.watch(sim_id, 0x0003e000, 0x2000),
 | 
			
		||||
            index: 0,
 | 
			
		||||
            generic_palette: false,
 | 
			
		||||
            index: params.index,
 | 
			
		||||
            generic_palette: params.generic_palette,
 | 
			
		||||
            params,
 | 
			
		||||
            scale: 1.0,
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -175,7 +181,7 @@ impl ObjectWindow {
 | 
			
		|||
        self.params.write(ObjectParams {
 | 
			
		||||
            index: self.index,
 | 
			
		||||
            generic_palette: self.generic_palette,
 | 
			
		||||
            ..ObjectParams::default()
 | 
			
		||||
            ..*self.params
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -233,17 +239,6 @@ struct ObjectParams {
 | 
			
		|||
    right_color: Color32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for ObjectParams {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            index: 0,
 | 
			
		||||
            generic_palette: false,
 | 
			
		||||
            left_color: Color32::from_rgb(0xff, 0x00, 0x00),
 | 
			
		||||
            right_color: Color32::from_rgb(0x00, 0xc6, 0xf0),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum Eye {
 | 
			
		||||
    Left,
 | 
			
		||||
    Right,
 | 
			
		||||
| 
						 | 
				
			
			@ -302,7 +297,7 @@ impl ObjectRenderer {
 | 
			
		|||
        let palette = if params.generic_palette {
 | 
			
		||||
            utils::generic_palette(color)
 | 
			
		||||
        } else {
 | 
			
		||||
            utils::parse_palette(palettes.read(8 + obj.data.palette_index * 2), &brts, color)
 | 
			
		||||
            utils::palette_colors(palettes.read(8 + obj.data.palette_index * 2), &brts, color)
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        for row in 0..8 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,24 +10,29 @@ pub fn generic_palette(color: Color32) -> [Color32; 4] {
 | 
			
		|||
    GENERIC_PALETTE.map(|brt| shade(brt, color))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn parse_palette(palette: u8, brts: &[u8; 8], color: Color32) -> [Color32; 4] {
 | 
			
		||||
    let shades = [
 | 
			
		||||
        Color32::BLACK,
 | 
			
		||||
        shade(brts[0], color),
 | 
			
		||||
        shade(brts[2], color),
 | 
			
		||||
        shade(
 | 
			
		||||
            brts[0].saturating_add(brts[2]).saturating_add(brts[4]),
 | 
			
		||||
            color,
 | 
			
		||||
        ),
 | 
			
		||||
    ];
 | 
			
		||||
pub const fn parse_palette(palette: u8) -> [u8; 4] {
 | 
			
		||||
    [
 | 
			
		||||
        Color32::BLACK,
 | 
			
		||||
        shades[(palette >> 2) as usize & 0x03],
 | 
			
		||||
        shades[(palette >> 4) as usize & 0x03],
 | 
			
		||||
        shades[(palette >> 6) as usize & 0x03],
 | 
			
		||||
        0,
 | 
			
		||||
        (palette >> 2) & 0x03,
 | 
			
		||||
        (palette >> 4) & 0x03,
 | 
			
		||||
        (palette >> 6) & 0x03,
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub const fn parse_shades(brts: &[u8; 8]) -> [u8; 4] {
 | 
			
		||||
    [
 | 
			
		||||
        0,
 | 
			
		||||
        brts[0],
 | 
			
		||||
        brts[2],
 | 
			
		||||
        brts[0].saturating_add(brts[2]).saturating_add(brts[4]),
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn palette_colors(palette: u8, brts: &[u8; 8], color: Color32) -> [Color32; 4] {
 | 
			
		||||
    let colors = parse_shades(brts).map(|s| shade(s, color));
 | 
			
		||||
    parse_palette(palette).map(|p| colors[p as usize])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct Object {
 | 
			
		||||
    pub x: i16,
 | 
			
		||||
    pub lon: bool,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,409 @@
 | 
			
		|||
use std::{fmt::Display, sync::Arc};
 | 
			
		||||
 | 
			
		||||
use egui::{
 | 
			
		||||
    CentralPanel, Checkbox, Color32, ComboBox, Context, Image, ScrollArea, Slider, TextureOptions,
 | 
			
		||||
    Ui, ViewportBuilder, ViewportId,
 | 
			
		||||
};
 | 
			
		||||
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
 | 
			
		||||
use num_derive::{FromPrimitive, ToPrimitive};
 | 
			
		||||
use num_traits::FromPrimitive;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    emulator::SimId,
 | 
			
		||||
    memory::{MemoryClient, MemoryView},
 | 
			
		||||
    vram::{VramImage, VramParams, VramProcessor, VramRenderer, VramTextureLoader},
 | 
			
		||||
    window::{
 | 
			
		||||
        utils::{NumberEdit, UiExt as _},
 | 
			
		||||
        AppWindow,
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::utils::{self, shade, Object};
 | 
			
		||||
 | 
			
		||||
pub struct WorldWindow {
 | 
			
		||||
    sim_id: SimId,
 | 
			
		||||
    loader: Arc<VramTextureLoader>,
 | 
			
		||||
    worlds: MemoryView,
 | 
			
		||||
    index: usize,
 | 
			
		||||
    generic_palette: bool,
 | 
			
		||||
    params: VramParams<WorldParams>,
 | 
			
		||||
    scale: f32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl WorldWindow {
 | 
			
		||||
    pub fn new(sim_id: SimId, memory: &MemoryClient, vram: &mut VramProcessor) -> 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) = vram.add(renderer, initial_params);
 | 
			
		||||
        let loader = VramTextureLoader::new([("vram://world".into(), world)]);
 | 
			
		||||
        Self {
 | 
			
		||||
            sim_id,
 | 
			
		||||
            loader: Arc::new(loader),
 | 
			
		||||
            worlds: memory.watch(sim_id, 0x3d800, 0x400),
 | 
			
		||||
            index: params.index,
 | 
			
		||||
            generic_palette: params.generic_palette,
 | 
			
		||||
            params,
 | 
			
		||||
            scale: 1.0,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn show_form(&mut self, ui: &mut Ui) {
 | 
			
		||||
        let row_height = ui.spacing().interact_size.y;
 | 
			
		||||
        ui.vertical(|ui| {
 | 
			
		||||
            TableBuilder::new(ui)
 | 
			
		||||
                .column(Column::auto())
 | 
			
		||||
                .column(Column::remainder())
 | 
			
		||||
                .body(|mut body| {
 | 
			
		||||
                    body.row(row_height, |mut row| {
 | 
			
		||||
                        row.col(|ui| {
 | 
			
		||||
                            ui.label("Index");
 | 
			
		||||
                        });
 | 
			
		||||
                        row.col(|ui| {
 | 
			
		||||
                            ui.add(NumberEdit::new(&mut self.index).range(0..32));
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            let 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("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.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"));
 | 
			
		||||
                            });
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
            });
 | 
			
		||||
            ui.section("Display", |ui| {
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    ui.label("Scale");
 | 
			
		||||
                    ui.spacing_mut().slider_width = ui.available_width();
 | 
			
		||||
                    let slider = Slider::new(&mut self.scale, 1.0..=10.0)
 | 
			
		||||
                        .step_by(1.0)
 | 
			
		||||
                        .show_value(false);
 | 
			
		||||
                    ui.add(slider);
 | 
			
		||||
                });
 | 
			
		||||
                ui.checkbox(&mut self.generic_palette, "Generic palette");
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
        self.params.write(WorldParams {
 | 
			
		||||
            index: self.index,
 | 
			
		||||
            generic_palette: self.generic_palette,
 | 
			
		||||
            ..*self.params
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn show_world(&mut self, ui: &mut Ui) {
 | 
			
		||||
        let image = Image::new("vram://world")
 | 
			
		||||
            .fit_to_original_size(self.scale)
 | 
			
		||||
            .texture_options(TextureOptions::NEAREST);
 | 
			
		||||
        ui.add(image);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AppWindow for WorldWindow {
 | 
			
		||||
    fn viewport_id(&self) -> ViewportId {
 | 
			
		||||
        ViewportId::from_hash_of(format!("world-{}", self.sim_id))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn sim_id(&self) -> SimId {
 | 
			
		||||
        self.sim_id
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn initial_viewport(&self) -> ViewportBuilder {
 | 
			
		||||
        ViewportBuilder::default()
 | 
			
		||||
            .with_title(format!("Worlds ({})", self.sim_id))
 | 
			
		||||
            .with_inner_size((640.0, 480.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))
 | 
			
		||||
                    .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,
 | 
			
		||||
    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),
 | 
			
		||||
            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 VramImage) {
 | 
			
		||||
        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(),
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl VramRenderer<1> for WorldRenderer {
 | 
			
		||||
    type Params = WorldParams;
 | 
			
		||||
 | 
			
		||||
    fn sizes(&self) -> [[usize; 2]; 1] {
 | 
			
		||||
        [[384, 224]]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn render(&mut self, params: &Self::Params, images: &mut [VramImage; 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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct World {
 | 
			
		||||
    header: WorldHeader,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl World {
 | 
			
		||||
    pub fn parse(data: &[u16; 16]) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            header: WorldHeader::parse(data[0]),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct WorldHeader {
 | 
			
		||||
    lon: bool,
 | 
			
		||||
    ron: bool,
 | 
			
		||||
    mode: WorldMode,
 | 
			
		||||
    over: bool,
 | 
			
		||||
    end: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 over = data & 0x0080 != 0;
 | 
			
		||||
        let end = data & 0x0040 != 0;
 | 
			
		||||
        Self {
 | 
			
		||||
            lon,
 | 
			
		||||
            ron,
 | 
			
		||||
            mode,
 | 
			
		||||
            over,
 | 
			
		||||
            end,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[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",
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue