View affine worlds
This commit is contained in:
parent
3cdc0583a6
commit
802da8f93e
|
@ -427,6 +427,12 @@ version = "1.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "az"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.74"
|
||||
|
@ -451,7 +457,7 @@ dependencies = [
|
|||
"bitflags 2.6.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools",
|
||||
"itertools 0.13.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
|
@ -834,6 +840,12 @@ version = "0.8.21"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
|
||||
|
||||
[[package]]
|
||||
name = "cursor-icon"
|
||||
version = "1.1.0"
|
||||
|
@ -1166,6 +1178,19 @@ dependencies = [
|
|||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixed"
|
||||
version = "1.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85c6e0b89bf864acd20590dbdbad56f69aeb898abfc9443008fd7bd48b2cc85a"
|
||||
dependencies = [
|
||||
"az",
|
||||
"bytemuck",
|
||||
"half",
|
||||
"num-traits",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.35"
|
||||
|
@ -1443,6 +1468,16 @@ dependencies = [
|
|||
"bitflags 2.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.2"
|
||||
|
@ -1691,6 +1726,15 @@ dependencies = [
|
|||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.14"
|
||||
|
@ -1779,10 +1823,11 @@ dependencies = [
|
|||
"egui-wgpu",
|
||||
"egui-winit",
|
||||
"egui_extras",
|
||||
"fixed",
|
||||
"gilrs",
|
||||
"hex",
|
||||
"image",
|
||||
"itertools",
|
||||
"itertools 0.14.0",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"oneshot",
|
||||
|
@ -3372,6 +3417,12 @@ dependencies = [
|
|||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "uds_windows"
|
||||
version = "1.1.0"
|
||||
|
|
|
@ -21,10 +21,11 @@ egui_extras = { version = "0.30", features = ["image"] }
|
|||
egui-toast = { git = "https://github.com/urholaukkarinen/egui-toast.git", rev = "d0bcf97" }
|
||||
egui-winit = "0.30"
|
||||
egui-wgpu = { version = "0.30", features = ["winit"] }
|
||||
fixed = { version = "1.28", features = ["num-traits"] }
|
||||
gilrs = { version = "0.11", features = ["serde-serialize"] }
|
||||
hex = "0.4"
|
||||
image = { version = "0.25", default-features = false, features = ["png"] }
|
||||
itertools = "0.13"
|
||||
itertools = "0.14"
|
||||
num-derive = "0.4"
|
||||
num-traits = "0.2"
|
||||
oneshot = "0.1"
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue