Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e5680ac6a | ||
|
|
504f63be54 | ||
|
|
30477254e5 | ||
|
|
4861374aef | ||
|
|
d3a688e768 |
@@ -0,0 +1,51 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
steps:
|
||||||
|
- name: Check out source
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
run: |
|
||||||
|
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-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: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install --no-install-recommends -y \
|
||||||
|
clang \
|
||||||
|
libasound2-dev \
|
||||||
|
libudev-dev \
|
||||||
|
libusb-1.0-0-dev \
|
||||||
|
libwayland-dev \
|
||||||
|
libx11-dev \
|
||||||
|
libxkbcommon-dev \
|
||||||
|
libxkbcommon-x11-0 \
|
||||||
|
mesa-vulkan-drivers \
|
||||||
|
mold \
|
||||||
|
pkg-config
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cargo build --locked -j 4
|
||||||
|
|
||||||
|
- name: Run unit tests
|
||||||
|
run: cargo test --locked --lib -j 4
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
(
|
(
|
||||||
id: TestFighter,
|
id: TestFighter,
|
||||||
model_path: "fighters/test/test-fighter-2.glb",
|
model_path: "fighters/test/test_fighter.gltf",
|
||||||
model_scale: "10.0",
|
model_scale: "10.0",
|
||||||
baked_animation_path: "fighters/test/test.fighterbake",
|
baked_animation_path: "fighters/test/test.fighterbake",
|
||||||
icon: (
|
icon: (
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@@ -1,84 +0,0 @@
|
|||||||
(
|
|
||||||
id: TestStage,
|
|
||||||
model_path: "stages/battlefield.glb#Scene0",
|
|
||||||
camera: (
|
|
||||||
left: -75.0,
|
|
||||||
right: 75.0,
|
|
||||||
bottom: -30.0,
|
|
||||||
top: 70.0,
|
|
||||||
origin: (0.0, 15.0),
|
|
||||||
vertical_fov_degrees: 30.0,
|
|
||||||
min_depth: 150.0,
|
|
||||||
max_depth: 400.0,
|
|
||||||
subject_scale: 1.0,
|
|
||||||
tracking_smoothness: 3.0,
|
|
||||||
vertical_tilt_degrees: 0.0,
|
|
||||||
horizontal_pan_coefficient: 0.0,
|
|
||||||
vertical_pan_coefficient: 0.0,
|
|
||||||
max_horizontal_pan_degrees: 17.5,
|
|
||||||
max_upward_pan_degrees: 5.0,
|
|
||||||
max_downward_pan_degrees: 7.0,
|
|
||||||
max_downward_expansion: 40.0,
|
|
||||||
),
|
|
||||||
polygons: [
|
|
||||||
(
|
|
||||||
poly_type: Platform,
|
|
||||||
lines: [
|
|
||||||
(
|
|
||||||
(x: "-59.5", y: "23.45"),
|
|
||||||
Floor
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(x: "-28.0", y: "23.45"),
|
|
||||||
Floor
|
|
||||||
)
|
|
||||||
]
|
|
||||||
),
|
|
||||||
(
|
|
||||||
poly_type: Platform,
|
|
||||||
lines: [
|
|
||||||
(
|
|
||||||
(x: "28.0", y: "23.45"),
|
|
||||||
Floor
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(x: "59.5", y: "23.45"),
|
|
||||||
Floor
|
|
||||||
)
|
|
||||||
]
|
|
||||||
),
|
|
||||||
(
|
|
||||||
poly_type: Closed,
|
|
||||||
lines: [
|
|
||||||
(
|
|
||||||
(x: "-56", y: "-200.0"),
|
|
||||||
Wall
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(x: "-56.0", y: "-3.5"),
|
|
||||||
Floor,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(x: "-39.2", y: "0.0"),
|
|
||||||
Floor,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(x: "0.0", y: "0.0"),
|
|
||||||
Floor
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(x: "39.2", y: "0.0"),
|
|
||||||
Floor
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(x: "56.0", y: "-3.5"),
|
|
||||||
Wall
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(x: "56", y: "-200.0"),
|
|
||||||
Wall
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 871 B |
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "nightly"
|
channel = "nightly-2026-09-14"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
io,
|
io,
|
||||||
|
path::Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
@@ -517,6 +518,7 @@ struct FighterBakeDeclaration {
|
|||||||
#[derive(Asset, TypePath)]
|
#[derive(Asset, TypePath)]
|
||||||
struct FighterBakeSource {
|
struct FighterBakeSource {
|
||||||
model_bytes: Vec<u8>,
|
model_bytes: Vec<u8>,
|
||||||
|
buffers: Vec<Vec<u8>>,
|
||||||
animations: HashMap<AnimKind, String>,
|
animations: HashMap<AnimKind, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,8 +552,31 @@ impl AssetLoader for FighterBakeSourceLoader {
|
|||||||
.read_asset_bytes(&manifest.model_path)
|
.read_asset_bytes(&manifest.model_path)
|
||||||
.await
|
.await
|
||||||
.map_err(|error| io::Error::other(error.to_string()))?;
|
.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 {
|
Ok(FighterBakeSource {
|
||||||
model_bytes,
|
model_bytes,
|
||||||
|
buffers,
|
||||||
animations: manifest.animations,
|
animations: manifest.animations,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -600,7 +625,7 @@ impl AssetTransformer for FighterBakeTransformer {
|
|||||||
source: TransformedAsset<Self::AssetInput>,
|
source: TransformedAsset<Self::AssetInput>,
|
||||||
_: &'a Self::Settings,
|
_: &'a Self::Settings,
|
||||||
) -> Result<TransformedAsset<Self::AssetOutput>, Self::Error> {
|
) -> 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))
|
Ok(source.replace_asset(baked))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -656,10 +681,43 @@ impl Plugin for BakedAnimationPlugin {
|
|||||||
|
|
||||||
fn bake_gltf(
|
fn bake_gltf(
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
|
buffer_bytes: &[Vec<u8>],
|
||||||
animations: &HashMap<AnimKind, String>,
|
animations: &HashMap<AnimKind, String>,
|
||||||
) -> io::Result<BakedFighterAnimations> {
|
) -> 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))?;
|
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
|
||||||
|
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(
|
||||||
|
document: gltf::Document,
|
||||||
|
buffers: Vec<gltf::buffer::Data>,
|
||||||
|
animations: &HashMap<AnimKind, String>,
|
||||||
|
) -> io::Result<BakedFighterAnimations> {
|
||||||
let nodes: Vec<_> = document.nodes().collect();
|
let nodes: Vec<_> = document.nodes().collect();
|
||||||
if nodes.is_empty() {
|
if nodes.is_empty() {
|
||||||
return Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
@@ -933,11 +991,12 @@ mod tests {
|
|||||||
fn configured_fighter_clips_bake_to_fixed_point_frames() {
|
fn configured_fighter_clips_bake_to_fixed_point_frames() {
|
||||||
let manifest: FighterManifest =
|
let manifest: FighterManifest =
|
||||||
ron::from_str(include_str!("../../assets/fighters/test/test.fighter.ron")).unwrap();
|
ron::from_str(include_str!("../../assets/fighters/test/test.fighter.ron")).unwrap();
|
||||||
let baked = bake_gltf(
|
let (document, buffers, _) = gltf::import(concat!(
|
||||||
include_bytes!("../../assets/fighters/test/test-fighter-2.glb"),
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
&manifest.animations,
|
"/assets/fighters/test/test_fighter.gltf"
|
||||||
)
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let baked = bake_gltf_document(document, buffers, &manifest.animations).unwrap();
|
||||||
|
|
||||||
assert_eq!(baked.frame_rate, BAKED_ANIMATION_FPS);
|
assert_eq!(baked.frame_rate, BAKED_ANIMATION_FPS);
|
||||||
assert_eq!(baked.frame_count(AnimKind::Wait), Some(121));
|
assert_eq!(baked.frame_count(AnimKind::Wait), Some(121));
|
||||||
|
|||||||
+1
-5
@@ -1,7 +1,3 @@
|
|||||||
//! Standalone binary for game-test: `cargo run` plays the game
|
|
||||||
//! without the editor. The editor's Play button runs the same
|
|
||||||
//! [`game_test::GamePlugin`] through its own game runner instead.
|
|
||||||
|
|
||||||
use bevy::asset::AssetMode::Processed;
|
use bevy::asset::AssetMode::Processed;
|
||||||
use bevy::camera_controller::free_camera::FreeCameraPlugin;
|
use bevy::camera_controller::free_camera::FreeCameraPlugin;
|
||||||
use bevy::image::{ImageAddressMode, ImagePlugin, ImageSamplerDescriptor};
|
use bevy::image::{ImageAddressMode, ImagePlugin, ImageSamplerDescriptor};
|
||||||
@@ -30,7 +26,7 @@ fn main() -> AppExit {
|
|||||||
App::new()
|
App::new()
|
||||||
.add_plugins(default_plugins)
|
.add_plugins(default_plugins)
|
||||||
.add_plugins(avian3d::prelude::PhysicsPlugins::default())
|
.add_plugins(avian3d::prelude::PhysicsPlugins::default())
|
||||||
.add_plugins(game_test::GamePlugin)
|
.add_plugins(shinespark::GamePlugin)
|
||||||
.add_plugins(FreeCameraPlugin)
|
.add_plugins(FreeCameraPlugin)
|
||||||
.add_plugins(EguiPlugin::default())
|
.add_plugins(EguiPlugin::default())
|
||||||
.add_plugins(WorldInspectorPlugin::default()) // adds default options and `InspectorEguiImpl`s
|
.add_plugins(WorldInspectorPlugin::default()) // adds default options and `InspectorEguiImpl`s
|
||||||
|
|||||||
Reference in New Issue
Block a user