View affine worlds

This commit is contained in:
2025-02-17 21:19:34 -05:00
parent 3cdc0583a6
commit 802da8f93e
4 changed files with 218 additions and 17 deletions
+20 -6
View File
@@ -9,7 +9,7 @@ use egui::{
Response, RichText, Rounding, Sense, Shape, Stroke, TextEdit, Ui, UiBuilder, Vec2, Widget,
WidgetText,
};
use num_traits::PrimInt;
use num_traits::{CheckedAdd, CheckedSub, One};
pub trait UiExt {
fn section(&mut self, title: impl Into<String>, add_contents: impl FnOnce(&mut Ui));
@@ -97,12 +97,20 @@ enum Direction {
Down,
}
pub trait Number: PrimInt + Display + FromStr + Send + Sync + 'static {}
impl<T: PrimInt + Display + FromStr + Send + Sync + 'static> Number for T {}
pub trait Number:
Copy + One + CheckedAdd + CheckedSub + Eq + Ord + Display + FromStr + Send + Sync + 'static
{
}
impl<
T: Copy + One + CheckedAdd + CheckedSub + Eq + Ord + Display + FromStr + Send + Sync + 'static,
> Number for T
{
}
pub struct NumberEdit<'a, T: Number> {
value: &'a mut T,
increment: T,
precision: usize,
min: Option<T>,
max: Option<T>,
}
@@ -112,11 +120,16 @@ impl<'a, T: Number> NumberEdit<'a, T> {
Self {
value,
increment: T::one(),
precision: 3,
min: None,
max: None,
}
}
pub fn precision(self, precision: usize) -> Self {
Self { precision, ..self }
}
pub fn range(self, range: impl RangeBounds<T>) -> Self {
let min = match range.start_bound() {
Bound::Unbounded => None,
@@ -135,18 +148,19 @@ impl<'a, T: Number> NumberEdit<'a, T> {
impl<T: Number> Widget for NumberEdit<'_, T> {
fn ui(self, ui: &mut Ui) -> Response {
let id = ui.id();
let to_string = |val: &T| format!("{val:.0$}", self.precision);
let (last_value, mut str, focus) = ui.memory(|m| {
let (lv, s) = m
.data
.get_temp(id)
.unwrap_or((*self.value, self.value.to_string()));
.unwrap_or((*self.value, to_string(self.value)));
let focus = m.has_focus(id);
(lv, s, focus)
});
let mut stale = false;
if *self.value != last_value {
str = self.value.to_string();
str = to_string(self.value);
stale = true;
}
let valid = str.parse().is_ok_and(|v: T| v == *self.value);
@@ -236,7 +250,7 @@ impl<T: Number> Widget for NumberEdit<'_, T> {
if let Some(new_value) = value.filter(in_range) {
*self.value = new_value;
}
str = self.value.to_string();
str = to_string(self.value);
stale = true;
} else if res.changed {
if let Some(new_value) = str.parse().ok().filter(in_range) {
+143 -8
View File
@@ -5,6 +5,10 @@ use egui::{
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;
@@ -277,10 +281,11 @@ impl WorldWindow {
ui.label("Index");
});
row.col(|ui| {
ui.add(NumberEdit::new(&mut self.param_index).range(0..32));
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;
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| {
@@ -314,6 +319,79 @@ impl WorldWindow {
});
});
});
} 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));
});
});
});
});
} else {
self.param_index = 0;
}
@@ -812,14 +890,19 @@ impl<'a> SourceCoordCalculator<'a> {
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::Normal => {
let sx = x + self.world.src_x - self.world.src_parallax;
let sy = y + self.world.src_y;
SourceParam::Affine(affine) => {
let sx = affine_coord(affine.src_x, x, affine.dx, affine.src_parallax.min(0).abs());
let sy = affine_coord(affine.src_y, x, affine.dy, affine.src_parallax.min(0).abs());
(sx, sy)
}
}
@@ -828,14 +911,19 @@ impl<'a> SourceCoordCalculator<'a> {
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::Normal => {
let sx = x + self.world.src_x + self.world.src_parallax;
let sy = y + self.world.src_y;
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)
}
}
@@ -849,6 +937,10 @@ impl<'a> SourceCoordCalculator<'a> {
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;
}
}
@@ -856,6 +948,7 @@ impl<'a> SourceCoordCalculator<'a> {
enum SourceParam {
Normal,
HBias(HBiasParam),
Affine(AffineParam),
}
struct HBiasParam {
@@ -870,3 +963,45 @@ impl HBiasParam {
Self { left, right }
}
}
struct AffineParam {
src_x: FixedI32<U3>,
src_parallax: i16,
src_y: FixedI32<U3>,
dx: FixedI32<U9>,
dy: FixedI32<U9>,
}
impl AffineParam {
fn load(params: &MemoryRef, index: usize) -> Self {
let src_x = params.read::<i16>(index & 0x1ffff);
let src_x = FixedI32::from_bits(src_x as i32);
let src_parallax = params.read::<i16>((index + 1) & 0x1ffff);
let src_y = params.read::<i16>((index + 2) & 0x1ffff);
let src_y = FixedI32::from_bits(src_y as i32);
let dx = params.read::<i16>((index + 3) & 0x1ffff);
let dx = FixedI32::from_bits(dx as i32);
let dy = params.read::<i16>((index + 4) & 0x1ffff);
let dy = FixedI32::from_bits(dy as i32);
AffineParam {
src_x,
src_parallax,
src_y,
dx,
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
}