Compare commits

..
Author SHA1 Message Date
EIREXE c849caf5b9 Try and fix CI
CI / build-and-test (pull_request) Successful in 24m33s
2026-09-14 21:34:35 +02:00
EIREXE 9f66d55dcc Add keyboard support and hold to step
CI / build-and-test (pull_request) Failing after 15m7s
2026-09-14 19:12:25 +02:00
EIREXE 578648d2ec Add debug frame stepping
CI / build-and-test (pull_request) Failing after 15m6s
2026-09-14 18:47:55 +02:00
EIREXE 0d96a8aed8 Make warnings an error
CI / build-and-test (push) Failing after 15m6s
2026-09-14 18:25:15 +02:00
EIREXE cebf1148c2 Persist Rust CI cache (#4)
CI / build-and-test (push) Successful in 11m1s
## Summary

- Pin the nightly Rust toolchain used by CI
- Cache Cargo dependencies and build artifacts between Gitea Actions runs
- Share the Linux nightly cache across branches

Reviewed-on: #4
Co-authored-by: Álex Román Núñez <eirexe@eirteam.moe>
2026-09-14 16:16:48 +00:00
EIREXE 088d594e97 Merge pull request 'Make debug menu usable with mouse and keyboard' (#2) from debug-menu-input into master
CI / build-and-test (push) Successful in 11m29s
Reviewed-on: #2
2026-09-14 13:32:42 +00:00
EIREXE 868df95cd4 wording fixes
CI / build-and-test (pull_request) Successful in 11m31s
2026-09-14 15:20:07 +02:00
EIREXE 504f63be54 Add support for split .gltf and .bin files (#3)
CI / build-and-test (push) Canceled after 15s
Reviewed-on: #3
Co-authored-by: Álex Román Núñez <eirexe@eirteam.moe>
2026-09-14 13:19:29 +00:00
EIREXE 8c11a01446 Make debug menu usable with mouse and keyboard
CI / build-and-test (pull_request) Successful in 11m27s
2026-09-14 14:33:04 +02:00
EIREXE 30477254e5 Add build and test CI (#1)
CI / build-and-test (push) Successful in 11m30s
Reviewed-on: #1
Co-authored-by: Álex Román Núñez <eirexe@eirteam.moe>
2026-09-14 12:02:24 +00:00
4 changed files with 422 additions and 123 deletions
+9 -3
View File
@@ -10,7 +10,7 @@ on:
jobs:
build-and-test:
runs-on: ubuntu-latest
timeout-minutes: 15
timeout-minutes: 30
steps:
- name: Check out source
uses: actions/checkout@v4
@@ -20,7 +20,13 @@ jobs:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
sh -s -- -y --profile minimal
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
"$HOME/.cargo/bin/rustup" toolchain install nightly --profile minimal
"$HOME/.cargo/bin/rustup" toolchain install nightly-2026-09-14 --profile minimal
- name: Cache Rust dependencies and build artifacts
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
shared-key: linux-nightly
cache-bin: false
- name: Install Linux dependencies
run: |
@@ -39,7 +45,7 @@ jobs:
pkg-config
- name: Build
run: cargo build --locked -j 4
run: RUSTFLAGS="-D warnings" cargo build --locked -j 4
- name: Run unit tests
run: cargo test --locked --lib -j 4
+1 -1
View File
@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly"
channel = "nightly-2026-09-14"
+359 -116
View File
@@ -3,11 +3,12 @@
use std::{collections::HashMap, time::Duration};
use bevy::{
input::InputSystems,
prelude::*,
settings::{ReflectSettingsGroup, SaveSettingsDeferred, SettingsGroup},
};
use bevy_egui::{EguiContexts, EguiPrimaryContextPass, egui};
use bevy_ggrs::RollbackFrameRate;
use bevy_ggrs::{RollbackFrameRate, RunGgrsSystems};
use enum_cycling::EnumCycle;
use enum_cycling_derive::EnumCycle;
@@ -48,7 +49,7 @@ impl PresentationMotionMode {
}
}
#[derive(EnumCycle, Reflect)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumCycle, Reflect)]
pub enum GameSpeedDebug {
Full,
Half,
@@ -114,6 +115,7 @@ enum DebugFlag {
Hurtboxes,
Camera,
DrawThroughGeometry,
FrameStepping,
}
#[derive(Clone, Copy)]
@@ -174,6 +176,10 @@ const WORLD_MENU: &[DebugMenuEntry] = &[
DebugMenuEntry::GameSpeed {
label: "Game speed",
},
DebugMenuEntry::Toggle {
label: "Frame stepping",
flag: DebugFlag::FrameStepping,
},
];
const DIAGNOSTICS_MENU: &[DebugMenuEntry] = &[DebugMenuEntry::Toggle {
@@ -233,6 +239,8 @@ pub struct DebugSettings {
selections: Vec<usize>,
#[reflect(ignore)]
pub player_rows: HashMap<usize, [FighterDebugRowKind; PLAYER_DEBUG_ROW_COUNT]>,
#[reflect(ignore)]
pub frame_stepping: bool,
pub network: bool,
pub ecb: bool,
pub stage_collision: bool,
@@ -253,6 +261,7 @@ impl Default for DebugSettings {
pages: vec![DebugMenuPage::Static(ROOT_MENU)],
selections: vec![0],
player_rows: HashMap::new(),
frame_stepping: false,
network: false,
ecb: false,
stage_collision: false,
@@ -325,6 +334,7 @@ impl DebugSettings {
DebugFlag::Hurtboxes => self.hurtboxes,
DebugFlag::DrawThroughGeometry => self.draw_through_geometry,
DebugFlag::Camera => self.camera,
DebugFlag::FrameStepping => self.frame_stepping,
}
}
@@ -340,6 +350,7 @@ impl DebugSettings {
self.draw_through_geometry = !self.draw_through_geometry
}
DebugFlag::Camera => self.camera = !self.camera,
DebugFlag::FrameStepping => self.frame_stepping = !self.frame_stepping,
}
}
@@ -356,9 +367,25 @@ impl DebugSettings {
pub struct DebugToolsPlugin;
#[derive(Resource, Default)]
struct FrameStepState {
requested: bool,
time_backup: Option<Time>,
last_step_time: Time
}
impl Plugin for DebugToolsPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<DebugSettings>()
.init_resource::<FrameStepState>()
.add_systems(
PreUpdate,
prepare_frame_step
.after(InputSystems)
.before(RunGgrsSystems)
.run_if(in_state(AppState::InMatch)),
)
.add_systems(PreUpdate, restore_frame_step_time.after(RunGgrsSystems))
.add_systems(
Update,
(handle_palette_input, sync_gizmo_depth_bias)
@@ -387,6 +414,141 @@ fn page_len(page: DebugMenuPage, player_handles: &[usize]) -> usize {
}
}
fn queue_settings_save(commands: &mut Commands) {
commands.queue(SaveSettingsDeferred(Duration::from_secs_f32(0.1)));
}
fn frame_step_duration(frame_rate: usize) -> Duration {
Duration::from_nanos(1_000_000_000u64 / frame_rate as u64)
}
fn prepare_frame_step(
settings: Res<DebugSettings>,
gamepads: Query<&Gamepad>,
key: Res<ButtonInput<KeyCode>>,
mut state: ResMut<FrameStepState>,
mut time: ResMut<Time>,
frame_rate: Res<RollbackFrameRate>,
) {
if !settings.frame_stepping {
state.requested = false;
return;
}
let pressed = gamepads
.iter()
.any(|gamepad| gamepad.pressed(GamepadButton::DPadDown));
let just_pressed = gamepads
.iter()
.any(|gamepad| gamepad.just_pressed(GamepadButton::DPadDown));
let just_pressed = key.just_pressed(KeyCode::F4) || just_pressed;
let pressed = key.pressed(KeyCode::F4) || pressed;
if just_pressed {
state.last_step_time = time.clone();
}
let can_repress = pressed && (time.elapsed() - state.last_step_time.elapsed()).as_millis() > 500;
if !settings.menu_open && (just_pressed || can_repress)
{
state.requested = true;
}
state.time_backup = Some(*time);
let delta = if state.requested {
frame_step_duration(**frame_rate)
} else {
Duration::ZERO
};
time.advance_by(delta);
}
fn restore_frame_step_time(mut state: ResMut<FrameStepState>, mut time: ResMut<Time>) {
if let Some(backup) = state.time_backup.take() {
*time = backup;
state.requested = false;
}
}
fn activate_selected(
settings: &mut DebugSettings,
page_len: usize,
player_handles: &[usize],
commands: &mut Commands,
) {
if page_len == 0 {
return;
}
match settings.page() {
DebugMenuPage::Static(entries) => match entries[settings.selected()] {
DebugMenuEntry::StopRecording { .. } => {
commands.insert_resource(StopReplayRecording);
}
DebugMenuEntry::Toggle { flag, .. } => {
settings.toggle_flag(flag);
if !matches!(flag, DebugFlag::FrameStepping) {
queue_settings_save(commands);
}
}
DebugMenuEntry::Submenu { entries, .. } => {
settings.push_page(DebugMenuPage::Static(entries));
}
DebugMenuEntry::PlayerSlots { .. } => settings.push_page(DebugMenuPage::PlayerList),
DebugMenuEntry::MotionSampling { .. } => {
settings.cycle_motion_sampling();
queue_settings_save(commands);
}
DebugMenuEntry::GameSpeed { .. } => {
commands.insert_resource(RollbackFrameRate(settings.game_speed.frame_rate()));
queue_settings_save(commands);
}
},
DebugMenuPage::PlayerList => {
let handle = player_handles[settings.selected()];
settings.rows_mut(handle);
settings.push_page(DebugMenuPage::PlayerRows(handle));
}
DebugMenuPage::PlayerRows(_) => {}
}
}
fn cycle_selected_value(settings: &mut DebugSettings, direction: i8, commands: &mut Commands) {
match settings.page() {
DebugMenuPage::Static(entries) => {
if let Some(entry) = entries.get(settings.selected()) {
match entry {
DebugMenuEntry::MotionSampling { .. } => {
settings.cycle_motion_sampling();
queue_settings_save(commands);
}
DebugMenuEntry::GameSpeed { .. } => {
settings.game_speed = if direction > 0 {
settings.game_speed.up()
} else {
settings.game_speed.down()
};
commands
.insert_resource(RollbackFrameRate(settings.game_speed.frame_rate()));
queue_settings_save(commands);
}
_ => {}
}
}
}
DebugMenuPage::PlayerRows(handle) => {
let selected = settings.selected();
let rows = settings.rows_mut(handle);
rows[selected] = rows[selected].cycle(direction);
queue_settings_save(commands);
}
DebugMenuPage::PlayerList => {}
}
}
fn handle_palette_input(
keyboard: Res<ButtonInput<KeyCode>>,
gamepads: Query<(Entity, &Gamepad)>,
@@ -398,6 +560,15 @@ fn handle_palette_input(
settings.toggle_flag(DebugFlag::DrawThroughGeometry);
}
if keyboard.just_pressed(KeyCode::F3) {
settings.menu_open = !settings.menu_open;
settings.controller = None;
if settings.menu_open {
settings.reset_navigation();
}
return;
}
if !settings.menu_open {
for (entity, gamepad) in &gamepads {
if gamepad.just_pressed(GamepadButton::Start) && gamepad.pressed(GamepadButton::DPadUp)
@@ -411,14 +582,15 @@ fn handle_palette_input(
return;
}
let Some(controller) = settings.controller else {
settings.menu_open = false;
return;
};
let Ok((_, gamepad)) = gamepads.get(controller) else {
settings.menu_open = false;
settings.controller = None;
return;
let gamepad = if let Some(controller) = settings.controller {
let Ok((_, gamepad)) = gamepads.get(controller) else {
settings.menu_open = false;
settings.controller = None;
return;
};
Some(gamepad)
} else {
None
};
let player_handles = player_handles(&players);
let page = settings.page();
@@ -428,95 +600,51 @@ fn handle_palette_input(
*settings.selected_mut() = 0;
}
if gamepad.just_pressed(GamepadButton::DPadUp) && page_len != 0 {
if (gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::DPadUp))
|| keyboard.just_pressed(KeyCode::ArrowUp)
|| keyboard.just_pressed(KeyCode::KeyW))
&& page_len != 0
{
let selected = settings.selected_mut();
*selected = selected.checked_sub(1).unwrap_or(page_len - 1);
}
if gamepad.just_pressed(GamepadButton::DPadDown) && page_len != 0 {
if (gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::DPadDown))
|| keyboard.just_pressed(KeyCode::ArrowDown)
|| keyboard.just_pressed(KeyCode::KeyS))
&& page_len != 0
{
let selected = settings.selected_mut();
*selected = (*selected + 1) % page_len;
}
let direction = if gamepad.just_pressed(GamepadButton::DPadLeft) {
let direction = if gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::DPadLeft))
|| keyboard.just_pressed(KeyCode::ArrowLeft)
|| keyboard.just_pressed(KeyCode::KeyA)
{
Some(-1)
} else if gamepad.just_pressed(GamepadButton::DPadRight) {
} else if gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::DPadRight))
|| keyboard.just_pressed(KeyCode::ArrowRight)
|| keyboard.just_pressed(KeyCode::KeyD)
{
Some(1)
} else {
None
};
if let Some(direction) = direction {
match page {
DebugMenuPage::Static(entries) => {
if let Some(entry) = entries.get(settings.selected()) {
match entry {
DebugMenuEntry::StopRecording { .. } => {}
DebugMenuEntry::MotionSampling { .. } => settings.cycle_motion_sampling(),
DebugMenuEntry::GameSpeed { .. } => {
if direction > 0 {
settings.game_speed = settings.game_speed.up();
} else {
settings.game_speed = settings.game_speed.down();
}
commands.insert_resource(RollbackFrameRate(
settings.game_speed.frame_rate(),
));
}
_ => {}
}
}
}
DebugMenuPage::PlayerList => todo!(),
DebugMenuPage::PlayerRows(_) => {
let DebugMenuPage::PlayerRows(handle) = page else {
unreachable!("player row navigation must have a player row page")
};
let selected = settings.selected();
let rows = settings.rows_mut(handle);
rows[selected] = rows[selected].cycle(direction);
commands.queue(SaveSettingsDeferred(Duration::from_secs_f32(0.1)));
}
}
cycle_selected_value(&mut settings, direction, &mut commands);
}
if matches!(page, DebugMenuPage::PlayerRows(_)) {
let direction = if gamepad.just_pressed(GamepadButton::DPadLeft) {
Some(-1)
} else if gamepad.just_pressed(GamepadButton::DPadRight) {
Some(1)
} else {
None
};
if let Some(_direction) = direction {}
if (gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::South))
|| keyboard.just_pressed(KeyCode::Enter)
|| keyboard.just_pressed(KeyCode::Space))
&& page_len != 0
{
activate_selected(&mut settings, page_len, &player_handles, &mut commands);
}
if gamepad.just_pressed(GamepadButton::South) {
match page {
DebugMenuPage::Static(entries) if page_len != 0 => match entries[settings.selected()] {
DebugMenuEntry::StopRecording { .. } => {
commands.insert_resource(StopReplayRecording);
}
DebugMenuEntry::Toggle { flag, .. } => {
settings.toggle_flag(flag);
commands.queue(SaveSettingsDeferred(Duration::from_secs_f32(0.1)));
}
DebugMenuEntry::Submenu { entries, .. } => {
settings.push_page(DebugMenuPage::Static(entries));
}
DebugMenuEntry::PlayerSlots { .. } => settings.push_page(DebugMenuPage::PlayerList),
DebugMenuEntry::MotionSampling { .. } => settings.cycle_motion_sampling(),
DebugMenuEntry::GameSpeed { .. } => {}
},
DebugMenuPage::PlayerList if page_len != 0 => {
let handle = player_handles[settings.selected()];
settings.rows_mut(handle);
settings.push_page(DebugMenuPage::PlayerRows(handle));
}
_ => {}
}
}
if gamepad.just_pressed(GamepadButton::West) && !settings.pop_page() {
if (gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::West))
|| keyboard.just_pressed(KeyCode::Escape))
&& !settings.pop_page()
{
settings.menu_open = false;
settings.controller = None;
}
@@ -538,8 +666,9 @@ fn sync_gizmo_depth_bias(settings: Res<DebugSettings>, mut configs: ResMut<Gizmo
fn draw_palette(
mut contexts: EguiContexts,
settings: Res<DebugSettings>,
mut settings: ResMut<DebugSettings>,
players: Query<&Player>,
mut commands: Commands,
) -> Result {
if !settings.menu_open {
return Ok(());
@@ -547,7 +676,7 @@ fn draw_palette(
let player_handles = player_handles(&players);
let page = settings.page();
let selected = settings.selected();
let page_len = page_len(page, &player_handles);
egui::Window::new("Debug")
.anchor(egui::Align2::RIGHT_TOP, egui::vec2(-12.0, 12.0))
.resizable(false)
@@ -556,38 +685,113 @@ fn draw_palette(
match page {
DebugMenuPage::Static(entries) => {
for (index, entry) in entries.iter().enumerate() {
let marker = if index == selected { ">" } else { " " };
match entry {
DebugMenuEntry::StopRecording { label } => {
ui.label(format!("{marker} {label}"));
ui.horizontal(|ui| {
ui.label(if index == settings.selected() {
">"
} else {
" "
});
match entry {
DebugMenuEntry::StopRecording { label } => {
if ui.button(*label).clicked() {
*settings.selected_mut() = index;
activate_selected(
&mut settings,
page_len,
&player_handles,
&mut commands,
);
}
}
DebugMenuEntry::Toggle { label, flag } => {
let mut value = settings.flag(*flag);
if ui.checkbox(&mut value, *label).changed() {
settings.toggle_flag(*flag);
if !matches!(flag, DebugFlag::FrameStepping) {
queue_settings_save(&mut commands);
}
}
}
DebugMenuEntry::Submenu { label, .. }
| DebugMenuEntry::PlayerSlots { label } => {
if ui.button(format!("{label} >")).clicked() {
*settings.selected_mut() = index;
activate_selected(
&mut settings,
page_len,
&player_handles,
&mut commands,
);
}
}
DebugMenuEntry::MotionSampling { label } => {
let old = settings.motion_sampling;
egui::ComboBox::from_id_salt(("debug-motion", index))
.width(180.0)
.selected_text(format!("{label}: {}", old.label()))
.show_ui(ui, |ui| {
ui.selectable_value(
&mut settings.motion_sampling,
PresentationMotionMode::Interpolation,
"Interpolation",
);
ui.selectable_value(
&mut settings.motion_sampling,
PresentationMotionMode::Extrapolation,
"Extrapolation",
);
});
if settings.motion_sampling != old {
queue_settings_save(&mut commands);
}
}
DebugMenuEntry::GameSpeed { label } => {
let old = settings.game_speed;
egui::ComboBox::from_id_salt(("debug-speed", index))
.width(180.0)
.selected_text(format!("{label}: {}", old.label()))
.show_ui(ui, |ui| {
for speed in [
GameSpeedDebug::Full,
GameSpeedDebug::Half,
GameSpeedDebug::Quarter,
] {
ui.selectable_value(
&mut settings.game_speed,
speed,
speed.label(),
);
}
});
if settings.game_speed != old {
commands.insert_resource(RollbackFrameRate(
settings.game_speed.frame_rate(),
));
queue_settings_save(&mut commands);
}
}
}
DebugMenuEntry::Toggle { label, flag } => {
let state = if settings.flag(*flag) { "on" } else { "off" };
ui.label(format!("{marker} {label}: {state}"));
}
DebugMenuEntry::Submenu { label, .. }
| DebugMenuEntry::PlayerSlots { label } => {
ui.label(format!("{marker} {label} >"));
}
DebugMenuEntry::MotionSampling { label } => {
ui.label(format!(
"{marker} {label}: {}",
settings.motion_sampling.label()
));
}
DebugMenuEntry::GameSpeed { label } => {
ui.label(format!(
"{marker} {label}: {}",
settings.game_speed.label()
));
}
}
});
}
}
DebugMenuPage::PlayerList => {
for (index, handle) in player_handles.iter().enumerate() {
let marker = if index == selected { ">" } else { " " };
ui.label(format!("{marker} Player {} >", handle + 1));
ui.horizontal(|ui| {
ui.label(if index == settings.selected() {
">"
} else {
" "
});
if ui.button(format!("Player {} >", handle + 1)).clicked() {
*settings.selected_mut() = index;
activate_selected(
&mut settings,
page_len,
&player_handles,
&mut commands,
);
}
});
}
if player_handles.is_empty() {
ui.label("No players in match");
@@ -600,16 +804,30 @@ fn draw_palette(
.unwrap_or([FighterDebugRowKind::None; PLAYER_DEBUG_ROW_COUNT]);
ui.label(format!("Player {}", handle + 1));
for (index, row) in rows.iter().enumerate() {
let marker = if index == selected { ">" } else { " " };
ui.label(format!("{marker} Row {}: {}", index + 1, row.label()));
ui.horizontal(|ui| {
ui.label(if index == settings.selected() {
">"
} else {
" "
});
egui::ComboBox::from_id_salt(("debug-row", handle, index))
.width(180.0)
.selected_text(format!("Row {}: {}", index + 1, row.label()))
.show_ui(ui, |ui| {
let rows = settings.rows_mut(handle);
for kind in FighterDebugRowKind::ALL {
ui.selectable_value(&mut rows[index], kind, kind.label());
}
});
});
}
}
}
ui.separator();
let controls = if matches!(page, DebugMenuPage::PlayerRows(_)) {
"D-pad: select/change B: back"
"Arrows/WASD: select/change Escape/B: back"
} else {
"D-pad: select A: enter/toggle Left/right: change B: back"
"Arrows/WASD: select Enter/A: activate Escape/B: back"
};
ui.label(controls);
});
@@ -699,4 +917,29 @@ mod tests {
mode.cycle();
assert_eq!(mode, PresentationMotionMode::Interpolation);
}
#[test]
fn game_speed_has_expected_frame_rates() {
assert_eq!(GameSpeedDebug::Full.frame_rate(), 60);
assert_eq!(GameSpeedDebug::Half.frame_rate(), 30);
assert_eq!(GameSpeedDebug::Quarter.frame_rate(), 15);
}
#[test]
fn frame_stepping_is_disabled_by_default_and_uses_game_speed_timestep() {
let settings = DebugSettings::default();
assert!(!settings.frame_stepping);
assert_eq!(frame_step_duration(60), Duration::from_nanos(16_666_666));
assert_eq!(frame_step_duration(30), Duration::from_nanos(33_333_333));
}
#[test]
fn empty_player_list_does_not_activate() {
let mut settings = DebugSettings::default();
settings.push_page(DebugMenuPage::PlayerList);
assert_eq!(page_len(settings.page(), &[]), 0);
assert_eq!(settings.selected(), 0);
}
}
+53 -3
View File
@@ -3,6 +3,7 @@
use std::{
collections::{HashMap, HashSet},
io,
path::Path,
};
use bevy::{
@@ -517,6 +518,7 @@ struct FighterBakeDeclaration {
#[derive(Asset, TypePath)]
struct FighterBakeSource {
model_bytes: Vec<u8>,
buffers: Vec<Vec<u8>>,
animations: HashMap<AnimKind, String>,
}
@@ -550,8 +552,31 @@ impl AssetLoader for FighterBakeSourceLoader {
.read_asset_bytes(&manifest.model_path)
.await
.map_err(|error| io::Error::other(error.to_string()))?;
let gltf = gltf::Gltf::from_slice(&model_bytes)
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
let mut buffers = Vec::with_capacity(gltf.buffers().len());
for buffer in gltf.buffers() {
let bytes = match buffer.source() {
gltf::buffer::Source::Uri(uri) => load_context
.read_asset_bytes(AssetPath::from_path_buf(
Path::new(&manifest.model_path)
.parent()
.unwrap_or_else(|| Path::new(""))
.join(uri),
))
.await
.map_err(|error| io::Error::other(error.to_string()))?,
gltf::buffer::Source::Bin => gltf.blob.clone().ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidData, "missing glTF binary blob")
})?,
};
buffers.push(bytes);
}
Ok(FighterBakeSource {
model_bytes,
buffers,
animations: manifest.animations,
})
}
@@ -600,7 +625,7 @@ impl AssetTransformer for FighterBakeTransformer {
source: TransformedAsset<Self::AssetInput>,
_: &'a Self::Settings,
) -> Result<TransformedAsset<Self::AssetOutput>, Self::Error> {
let baked = bake_gltf(&source.model_bytes, &source.animations)?;
let baked = bake_gltf(&source.model_bytes, &source.buffers, &source.animations)?;
Ok(source.replace_asset(baked))
}
}
@@ -656,11 +681,36 @@ impl Plugin for BakedAnimationPlugin {
fn bake_gltf(
bytes: &[u8],
buffer_bytes: &[Vec<u8>],
animations: &HashMap<AnimKind, String>,
) -> io::Result<BakedFighterAnimations> {
let (document, buffers, _) = gltf::import_slice(bytes)
let gltf = gltf::Gltf::from_slice(bytes)
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
bake_gltf_document(document, buffers, animations)
let buffers = gltf
.buffers()
.zip(buffer_bytes)
.map(|(buffer, bytes)| {
if bytes.len() < buffer.length() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"glTF buffer {} is too short: expected {}, got {}",
buffer.index(),
buffer.length(),
bytes.len()
),
));
}
Ok(gltf::buffer::Data(bytes.clone()))
})
.collect::<io::Result<Vec<_>>>()?;
if buffers.len() != gltf.buffers().len() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"glTF buffer data is incomplete",
));
}
bake_gltf_document(gltf.document, buffers, animations)
}
fn bake_gltf_document(