Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for repeating texture wrap modes. #238

Merged
merged 5 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,12 +245,16 @@ use bevy_asset_loader::asset_collection::AssetCollection;
#[derive(AssetCollection, Resource)]
struct ImageAssets {
#[asset(path = "images/pixel_tree.png")]
#[asset(image(sampler = linear))]
#[asset(image(sampler(filter = linear)))]
tree_linear: Handle<Image>,

#[asset(path = "images/pixel_tree.png")]
#[asset(image(sampler = nearest))]
#[asset(image(sampler(filter = nearest)))]
tree_nearest: Handle<Image>,

#[asset(path = "images/pixel_tree.png")]
#[asset(image(sampler(filter = linear, wrap = repeat)))]
tree_linear_repeat: Handle<Image>,
}
```

Expand All @@ -260,11 +264,18 @@ The corresponding dynamic asset would be
({
"tree_nearest": Image (
path: "images/tree.png",
sampler: Nearest
filter: Nearest,
wrap: Clamp
),
"tree_linear": Image (
path: "images/tree.png",
sampler: Linear
filter: Linear,
wrap: Clamp
),
"tree_linear_repeat": Image (
path: "images/tree.png",
filter: Linear,
wrap: Repeat
),
})
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
Image(
path: "images/female_adventurer_sheet.png",
sampler: Nearest,
wrap: Repeat,
),
Image(
path: "images/female_adventurer_sheet.png",
Expand Down
5 changes: 5 additions & 0 deletions bevy_asset_loader/assets/full_dynamic_collection.assets.ron
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
path: "images/tree.png",
sampler: Nearest
),
"pixel_tree_repeat": Image (
path: "images/tree.png",
sampler: Nearest,
wrap: Repeat
),
"array_texture": Image (
path: "images/array_texture.png",
array_texture_layers: 4
Expand Down
2 changes: 1 addition & 1 deletion bevy_asset_loader/examples/atlas_from_grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ struct MyAssets {
#[asset(texture_atlas_layout(tile_size_x = 96, tile_size_y = 99, columns = 8, rows = 1))]
female_adventurer_layout: Handle<TextureAtlasLayout>,
// you can configure the sampler for the sprite sheet image
#[asset(image(sampler = nearest))]
#[asset(image(sampler(filter = nearest)))]
#[asset(path = "images/female_adventurer_sheet.png")]
female_adventurer: Handle<Image>,
}
Expand Down
2 changes: 1 addition & 1 deletion bevy_asset_loader/examples/full_collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ struct MyAssets {

// Image asset with sampler nearest (good for crisp pixel art)
#[asset(path = "images/pixel_tree.png")]
#[asset(image(sampler = nearest))]
#[asset(image(sampler(filter = nearest)))]
image_tree_nearest: Handle<Image>,
// Array texture
#[asset(path = "images/array_texture.png")]
Expand Down
23 changes: 21 additions & 2 deletions bevy_asset_loader/examples/full_dynamic_collection.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use bevy::app::AppExit;
use bevy::asset::RecursiveDependencyLoadState;
use bevy::prelude::*;
use bevy::render::texture::{ImageSampler, ImageSamplerDescriptor};
use bevy::render::texture::{ImageAddressMode, ImageSampler, ImageSamplerDescriptor};
use bevy::utils::HashMap;
use bevy_asset_loader::prelude::*;

Expand Down Expand Up @@ -54,6 +54,9 @@ struct MyAssets {
// Image asset with sampler nearest (good for crisp pixel art)
#[asset(key = "pixel_tree")]
image_tree_nearest: Handle<Image>,
// Image asset with sampler nearest and address mode repeat
#[asset(key = "pixel_tree_repeat")]
image_tree_nearest_repeat: Handle<Image>,
// Array texture
#[asset(key = "array_texture")]
array_texture: Handle<Image>,
Expand Down Expand Up @@ -144,12 +147,28 @@ fn expectations(
.get(&assets.image_tree_nearest)
.expect("Image should be added to its asset resource");
let ImageSampler::Descriptor(descriptor) = &image.sampler else {
panic!("Descriptor was not set to non default value nearest");
panic!("Descriptor was not set to non default value");
};
assert_eq!(
descriptor.as_wgpu(),
ImageSamplerDescriptor::nearest().as_wgpu()
);
let image = images
.get(&assets.image_tree_nearest_repeat)
.expect("Image should be added to its asset resource");
let ImageSampler::Descriptor(descriptor) = &image.sampler else {
panic!("Descriptor was not set to non default value");
};
assert_eq!(
descriptor.as_wgpu(),
ImageSamplerDescriptor {
address_mode_u: ImageAddressMode::Repeat,
address_mode_v: ImageAddressMode::Repeat,
address_mode_w: ImageAddressMode::Repeat,
..ImageSamplerDescriptor::nearest()
}
.as_wgpu()
);

let image = images
.get(&assets.array_texture)
Expand Down
51 changes: 40 additions & 11 deletions bevy_asset_loader/examples/image_asset.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use bevy::math::Affine2;
use bevy::prelude::*;
use bevy_asset_loader::prelude::*;

/// This example demonstrates how you can set a different sampler for an [`Image`].
/// This example demonstrates how you can set different samplers and wrap modes for
/// an [`Image`] asset.
fn main() {
App::new()
.add_plugins(DefaultPlugins)
Expand All @@ -18,38 +20,65 @@ fn main() {
#[derive(AssetCollection, Resource)]
struct ImageAssets {
#[asset(path = "images/pixel_tree.png")]
#[asset(image(sampler = linear))]
#[asset(image(sampler(filter = linear)))]
tree_linear: Handle<Image>,

#[asset(path = "images/pixel_tree.png")]
#[asset(image(sampler = nearest))]
#[asset(image(sampler(filter = nearest)))]
tree_nearest: Handle<Image>,

#[asset(path = "images/pixel_tree.png")]
#[asset(image(sampler(filter = nearest, wrap = repeat)))]
tree_nearest_repeat: Handle<Image>,

#[asset(path = "images/array_texture.png")]
#[asset(image(array_texture_layers = 4))]
array_texture: Handle<Image>,
}

fn draw(mut commands: Commands, image_assets: Res<ImageAssets>) {
commands.spawn(Camera2dBundle {
projection: OrthographicProjection {
far: 1000.,
near: -1000.,
scale: 0.25,
fn draw(
mut commands: Commands,
image_assets: Res<ImageAssets>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(0.0, 1.5, 4.0).looking_at(Vec3::ZERO, Vec3::Y),
camera: Camera {
order: 1,
..default()
},
..default()
});
commands.spawn(Camera2dBundle::default());
commands.spawn(SpriteBundle {
texture: image_assets.tree_linear.clone(),
transform: Transform::from_translation(Vec3::new(-50., 0., 1.)),
transform: Transform::from_translation(Vec3::new(-50., 0., 1.)).with_scale(Vec3::splat(5.)),
..Default::default()
});
commands.spawn(SpriteBundle {
texture: image_assets.tree_nearest.clone(),
transform: Transform::from_translation(Vec3::new(50., 0., 1.)),
transform: Transform::from_translation(Vec3::new(50., 0., 1.)).with_scale(Vec3::splat(5.)),
..Default::default()
});
commands.spawn(PbrBundle {
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)),
material: materials.add(StandardMaterial {
base_color_texture: Some(image_assets.tree_nearest_repeat.clone()),
uv_transform: Affine2::from_scale(Vec2::new(2., 3.)),
..default()
}),
transform: Transform::from_xyz(1.5, 0.0, 0.0),
..default()
});
commands.spawn(PointLightBundle {
point_light: PointLight {
shadows_enabled: true,
..default()
},
transform: Transform::from_xyz(4.0, 8.0, 4.0),
..default()
});
}

fn assert(images: Res<ImageAssets>, image_assets: Res<Assets<Image>>) {
Expand Down
70 changes: 51 additions & 19 deletions bevy_asset_loader/src/standard_dynamic_asset.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::dynamic_asset::{DynamicAsset, DynamicAssetType};
use crate::dynamic_asset::{DynamicAssetCollection, DynamicAssets};
use bevy::asset::{Asset, AssetServer, Assets, LoadedFolder, UntypedHandle};
use bevy::ecs::change_detection::Res;
use bevy::ecs::system::SystemState;
use bevy::ecs::world::{Command, World};
use bevy::reflect::TypePath;
Expand All @@ -11,12 +12,15 @@ use serde::{Deserialize, Serialize};
use bevy::math::UVec2;
#[cfg(feature = "3d")]
use bevy::pbr::StandardMaterial;
use bevy::prelude::{Res, ResMut};
#[cfg(feature = "2d")]
use bevy::sprite::TextureAtlasLayout;

#[cfg(any(feature = "3d", feature = "2d"))]
use bevy::render::texture::{Image, ImageSampler, ImageSamplerDescriptor};
use bevy::ecs::change_detection::ResMut;
#[cfg(any(feature = "3d", feature = "2d"))]
use bevy::render::texture::{
Image, ImageAddressMode, ImageFilterMode, ImageSampler, ImageSamplerDescriptor,
};

/// These asset variants can be loaded from configuration files. They will then replace
/// a dynamic asset based on their keys.
Expand Down Expand Up @@ -48,8 +52,11 @@ pub enum StandardDynamicAsset {
/// Image file path
path: String,
/// Sampler
#[serde(with = "optional", skip_serializing_if = "Option::is_none", default)]
sampler: Option<ImageSamplerType>,
#[serde(default, skip_serializing_if = "is_default")]
sampler: ImageSamplerType,
/// Sampler
#[serde(default, skip_serializing_if = "is_default")]
wrap: ImageAddressModeType,
/// array texture layers
#[serde(with = "optional", skip_serializing_if = "Option::is_none", default)]
array_texture_layers: Option<u32>,
Expand Down Expand Up @@ -111,32 +118,49 @@ mod optional {
}
}

#[cfg(any(feature = "3d", feature = "2d"))]
fn is_default<T: Default + PartialEq + Copy>(value: &T) -> bool {
T::default() == *value
}

/// Define the image sampler to configure for an image asset
#[cfg(any(feature = "3d", feature = "2d"))]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Default)]
pub enum ImageSamplerType {
/// See [`ImageSampler::nearest`]
#[default]
Nearest,
/// See [`ImageSampler::linear`]
Linear,
}

#[cfg(any(feature = "3d", feature = "2d"))]
impl From<ImageSamplerType> for ImageSamplerDescriptor {
fn from(value: ImageSamplerType) -> Self {
impl From<&ImageSamplerType> for ImageFilterMode {
fn from(value: &ImageSamplerType) -> Self {
match value {
ImageSamplerType::Nearest => ImageSamplerDescriptor::nearest(),
ImageSamplerType::Linear => ImageSamplerDescriptor::linear(),
ImageSamplerType::Nearest => ImageFilterMode::Nearest,
ImageSamplerType::Linear => ImageFilterMode::Linear,
}
}
}

/// Define the image sampler address mode
#[cfg(any(feature = "3d", feature = "2d"))]
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Default)]
pub enum ImageAddressModeType {
/// See [`ImageAddressMode::ClampToEdge`]
#[default]
ClampToEdge,
/// See [`ImageAddressMode::Repeat`]
Repeat,
}

#[cfg(any(feature = "3d", feature = "2d"))]
impl From<ImageSamplerType> for ImageSampler {
fn from(value: ImageSamplerType) -> Self {
impl From<&ImageAddressModeType> for ImageAddressMode {
fn from(value: &ImageAddressModeType) -> Self {
match value {
ImageSamplerType::Nearest => ImageSampler::nearest(),
ImageSamplerType::Linear => ImageSampler::linear(),
ImageAddressModeType::ClampToEdge => ImageAddressMode::ClampToEdge,
ImageAddressModeType::Repeat => ImageAddressMode::Repeat,
}
}
}
Expand Down Expand Up @@ -179,15 +203,14 @@ impl DynamicAsset for StandardDynamicAsset {
StandardDynamicAsset::Image {
path,
sampler,
wrap: address_mode,
array_texture_layers,
} => {
let mut system_state =
SystemState::<(ResMut<Assets<Image>>, Res<AssetServer>)>::new(world);
let (mut images, asset_server) = system_state.get_mut(world);
let mut handle = asset_server.load(path);
if let Some(sampler) = sampler {
Self::update_image_sampler(&mut handle, &mut images, sampler);
}
Self::update_image_sampler(&mut handle, &mut images, sampler, address_mode);
if let Some(layers) = array_texture_layers {
let image = images
.get_mut(&handle)
Expand Down Expand Up @@ -273,21 +296,30 @@ impl StandardDynamicAsset {
handle: &mut bevy::asset::Handle<Image>,
images: &mut Assets<Image>,
sampler_type: &ImageSamplerType,
address_mode: &ImageAddressModeType,
) {
let image = images.get_mut(&*handle).unwrap();
let configured_descriptor = ImageSamplerDescriptor {
address_mode_u: address_mode.into(),
address_mode_v: address_mode.into(),
address_mode_w: address_mode.into(),
mag_filter: sampler_type.into(),
min_filter: sampler_type.into(),
mipmap_filter: sampler_type.into(),
..Default::default()
};
let is_different_sampler = if let ImageSampler::Descriptor(descriptor) = &image.sampler {
let configured_descriptor: ImageSamplerDescriptor = sampler_type.clone().into();
!descriptor.as_wgpu().eq(&configured_descriptor.as_wgpu())
} else {
false
};

if is_different_sampler {
let mut cloned_image = image.clone();
cloned_image.sampler = sampler_type.clone().into();
cloned_image.sampler = ImageSampler::Descriptor(configured_descriptor);
*handle = images.add(cloned_image);
} else {
image.sampler = sampler_type.clone().into();
image.sampler = ImageSampler::Descriptor(configured_descriptor);
}
}
}
Expand Down
Loading
Loading