Refactoring, reorganization and cleanup (#7)

### New `math` module
 * Traits for error-tolerant floating-point comparisons
 * Traits for interpolation and easing
 * Bevy plugin to manage 3D interpolations with ECS components

### `skybox` module refactor
 * ECS resource to enable external skybox selection/updates
 * System run conditions for more idiomatic/ECS-friendly control flow

### Camera controller refactor
 * Renamed from `orbit_cam` to `camera_controller`
 * Now delegates to the new interpolation plugin for WASD motion smoothing
 * Simple API for attaching the `CameraController` component to the camera from
   another module -- this resolves an awkward separation-of-concerns situation
	where we had to configure a bunch of render-quality settings in the camera
	controller module

### Scene module refactor
 * Renamed from `test_scene` to `scene`
 * This module is now responsible for configuring the skybox
 * This module is now responsible for spawning the camera and configuring the
   render settings
This commit is contained in:
Danny McGee
2023-10-31 16:39:10 -04:00
committed by GitHub
parent 4b9ea61495
commit ce6db7fc3c
11 changed files with 811 additions and 399 deletions
+68 -56
View File
@@ -11,79 +11,91 @@ pub struct SkyboxPlugin;
impl Plugin for SkyboxPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup)
.add_systems(Update, asset_loaded);
app.add_systems(
Update,
(
load_skybox.run_if(resource_exists_and_changed::<SkyboxId>()),
spawn_skybox
.run_if(resource_exists::<SkyboxAssets>())
.run_if(skybox_assets_loaded),
),
);
}
}
#[derive(Resource, Deref, DerefMut)]
pub struct SkyboxId(pub String);
#[derive(Resource)]
pub struct Cubemap {
is_loaded: bool,
image_handle: Handle<Image>,
struct SkyboxAssets {
cubemap: Handle<Image>,
env_diffuse: Handle<Image>,
env_specular: Handle<Image>,
}
impl Cubemap {
fn new(name: impl fmt::Display, assets: &Res<AssetServer>) -> Self {
let paths = SkyboxPaths::new(name);
Self {
is_loaded: false,
image_handle: assets.load(paths.main),
env_diffuse: assets.load(paths.env_diffuse),
env_specular: assets.load(paths.env_specular),
}
}
fn load_skybox(mut cmd: Commands, assets: Res<AssetServer>, id: Res<SkyboxId>) {
let paths = SkyboxPaths::new(&**id);
cmd.insert_resource(SkyboxAssets {
cubemap: assets.load(paths.cubemap),
env_diffuse: assets.load(paths.env_diffuse),
env_specular: assets.load(paths.env_specular),
});
}
pub struct SkyboxPaths {
pub main: String,
pub env_diffuse: String,
pub env_specular: String,
fn skybox_assets_loaded(
assets: Res<AssetServer>,
skybox_assets: Option<Res<SkyboxAssets>>,
) -> bool {
let Some(skybox_assets) = skybox_assets else {
return false;
};
assets.get_load_state(skybox_assets.cubemap.clone_weak()) == LoadState::Loaded
&& assets.get_load_state(skybox_assets.env_diffuse.clone_weak()) == LoadState::Loaded
&& assets.get_load_state(skybox_assets.env_specular.clone_weak()) == LoadState::Loaded
}
fn spawn_skybox(
mut cmd: Commands,
mut images: ResMut<Assets<Image>>,
skybox_assets: Res<SkyboxAssets>,
q_camera: Query<Entity, With<Camera3d>>,
) {
// Configure the skybox texture as a cubemap
let image = images.get_mut(&skybox_assets.cubemap).unwrap();
if image.texture_descriptor.array_layer_count() == 1 {
image.reinterpret_stacked_2d_as_array(6);
image.texture_view_descriptor = Some(TextureViewDescriptor {
dimension: Some(TextureViewDimension::Cube),
..default()
});
}
// Add components to the scene camera
cmd.entity(q_camera.single()).insert((
Skybox(skybox_assets.cubemap.clone()),
EnvironmentMapLight {
diffuse_map: skybox_assets.env_diffuse.clone(),
specular_map: skybox_assets.env_specular.clone(),
},
));
// Remove the intermediate resource
cmd.remove_resource::<SkyboxAssets>();
}
struct SkyboxPaths {
cubemap: String,
env_diffuse: String,
env_specular: String,
}
impl SkyboxPaths {
fn new(name: impl fmt::Display) -> Self {
Self {
main: format!("skyboxes/{name}.hdr"),
cubemap: format!("skyboxes/{name}.hdr"),
env_diffuse: format!("skyboxes/{name}_diffuse.zstd.ktx2"),
env_specular: format!("skyboxes/{name}_specular.zstd.ktx2"),
}
}
}
fn setup(mut cmd: Commands, assets: Res<AssetServer>) {
cmd.insert_resource(Cubemap::new("test_scene", &assets));
}
fn asset_loaded(
assets: Res<AssetServer>,
camera: Query<Entity, With<Camera3d>>,
mut cmd: Commands,
mut images: ResMut<Assets<Image>>,
mut cubemap: ResMut<Cubemap>,
) {
if !cubemap.is_loaded
&& assets.get_load_state(cubemap.image_handle.clone_weak()) == LoadState::Loaded
{
let image = images.get_mut(&cubemap.image_handle).unwrap();
if image.texture_descriptor.array_layer_count() == 1 {
image.reinterpret_stacked_2d_as_array(6);
image.texture_view_descriptor = Some(TextureViewDescriptor {
dimension: Some(TextureViewDimension::Cube),
..default()
});
}
cmd.entity(camera.single()).insert((
Skybox(cubemap.image_handle.clone()),
EnvironmentMapLight {
diffuse_map: cubemap.env_diffuse.clone(),
specular_map: cubemap.env_specular.clone(),
},
));
cubemap.is_loaded = true;
}
}