tirbofish/dropbear · diff
feature: improved lighting
feature: changed to uuid-based id system for assets
Signature present but could not be verified.
Unverified
@@ -48,6 +48,7 @@ image = {workspace = true, features = ["serde"]} puffin.workspace = true bitflags.workspace = true puffin_http.workspace = true +uuid.workspace = true float-derive.workspace = true rkyv.workspace = true bevy_mikktspace.workspace = true @@ -79,7 +79,7 @@ impl Default for LightArrayUniform { Self { lights: [LightUniform::default(); MAX_LIGHTS], light_count: 0, - ambient_strength: 0.1, + ambient_strength: 0.5, _padding: [0; 2], } } @@ -1,7 +1,15 @@ use crate::pipelines::create_render_pipeline; use crate::texture::{Texture, TextureBuilder}; +use wgpu::util::DeviceExt; use wgpu::Operations; +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +struct PostProcessUniforms { + gamma: f32, + _pad: [f32; 3], +} + pub struct HdrPipeline { pipeline: wgpu::RenderPipeline, bind_group: wgpu::BindGroup, @@ -12,6 +20,8 @@ pub struct HdrPipeline { format: wgpu::TextureFormat, layout: wgpu::BindGroupLayout, antialiasing: crate::multisampling::AntiAliasingMode, + gamma: f32, + gamma_buffer: wgpu::Buffer, } impl HdrPipeline { @@ -49,6 +59,16 @@ impl HdrPipeline { ), }; + let default_gamma = 2.2_f32; + let gamma_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Hdr::gamma_buffer"), + contents: bytemuck::bytes_of(&PostProcessUniforms { + gamma: default_gamma, + _pad: [0.0; 3], + }), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("Hdr::layout"), entries: &[ @@ -69,6 +89,16 @@ impl HdrPipeline { ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: None, }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, ], }); let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { @@ -83,6 +113,10 @@ impl HdrPipeline { binding: 1, resource: wgpu::BindingResource::Sampler(&texture.sampler), }, + wgpu::BindGroupEntry { + binding: 2, + resource: gamma_buffer.as_entire_binding(), + }, ], }); @@ -118,9 +152,33 @@ impl HdrPipeline { height, format, antialiasing, + gamma: default_gamma, + gamma_buffer, } } + /// Returns the current gamma exponent. + pub fn gamma(&self) -> f32 { + self.gamma + } + + /// Sets the gamma correction exponent applied after ACES tonemapping. + /// + /// - `2.2` — standard gamma for non-sRGB render targets (default). + /// - `1.0` — no correction; use when the render target is an sRGB-format + /// texture so the GPU handles gamma encoding automatically. + pub fn set_gamma(&mut self, queue: &wgpu::Queue, gamma: f32) { + self.gamma = gamma; + queue.write_buffer( + &self.gamma_buffer, + 0, + bytemuck::bytes_of(&PostProcessUniforms { + gamma, + _pad: [0.0; 3], + }), + ); + } + /// Resize the HDR texture pub fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32, antialiasing: Option<crate::multisampling::AntiAliasingMode>) { self.antialiasing = antialiasing.unwrap_or(self.antialiasing); @@ -157,6 +215,10 @@ impl HdrPipeline { binding: 1, resource: wgpu::BindingResource::Sampler(&self.texture.sampler), }, + wgpu::BindGroupEntry { + binding: 2, + resource: self.gamma_buffer.as_entire_binding(), + }, ], }); self.width = width; @@ -47,9 +47,21 @@ var hdr_image: texture_2d<f32>; @binding(1) var hdr_sampler: sampler; +struct PostProcessSettings { + gamma: f32, + _pad0: f32, + _pad1: f32, + _pad2: f32, +} + +@group(0) +@binding(2) +var<uniform> post_process: PostProcessSettings; + @fragment fn fs_main(vs: VertexOutput) -> @location(0) vec4<f32> { let hdr = textureSample(hdr_image, hdr_sampler, vs.uv); let sdr = aces_tone_map(hdr.rgb); - return vec4(sdr, hdr.a); + let gamma_corrected = pow(sdr, vec3<f32>(1.0 / post_process.gamma)); + return vec4(gamma_corrected, hdr.a); } @@ -429,6 +429,6 @@ fn s_fs_main(in: VertexOutput) -> @location(0) vec4<f32> { // combine let colour = lo + ambient_ibl + emissive; - + return vec4<f32>(colour, albedo_sample.a); } @@ -4,6 +4,7 @@ use crate::asset::AssetRegistry; use crate::graphics::SharedGraphicsContext; use crate::utils::{ResourceReference}; use image::{DynamicImage, GenericImageView, RgbaImage}; +use uuid::Uuid; use rkyv::Archive; use serde::{Deserialize, Serialize}; use wgpu::{SamplerDescriptor, TextureAspect, TextureFormat, TextureUsages, TextureViewDescriptor, TextureViewDimension}; @@ -143,8 +144,10 @@ impl Image { #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] pub enum TextureReference { - Resource(ResourceReference), + /// A solid RGBA colour literal — no file backing needed. RGBAColour([f32; 4]), + /// UUID of an asset tracked by a `.eucmeta` sidecar file. + AssetUuid(Uuid), } impl<'a> TextureBuilder<'a> { @@ -654,31 +654,43 @@ impl Component for MeshRenderer { let get_tex_handle = async |resource: &Option<TextureReference>| -> Option<anyhow::Result<Handle<Texture>>> { match resource { - Some(TextureReference::Resource(dif)) => match dif { - ResourceReference::File(file_path) if !file_path.is_empty() => { - let path = dif.resolve().ok()?; - let bytes = std::fs::read(&path).ok()?; - let mut texture = TextureBuilder::new(&graphics.device) - .with_bytes(graphics.clone(), bytes.as_slice()) - .label(file_path.as_str()) - .build(); - texture.reference = Some(dif.clone()); - let mut registry = ASSET_REGISTRY.write(); - Some(Ok(registry.add_texture_with_label(file_path.clone(), texture))) - } - ResourceReference::Embedded(bytes) => { - let texture = TextureBuilder::new(&graphics.device) - .with_bytes(graphics.clone(), bytes) - .label(label.as_str()) - .build(); - let mut registry = ASSET_REGISTRY.write(); - Some(Ok(registry.add_texture_with_label(label.clone(), texture))) - } - ResourceReference::Procedural(_) => { - Some(Err(anyhow::anyhow!("Using a Procedural object as a texture is not valid, for texture with label {}", label))) + Some(TextureReference::AssetUuid(uuid)) => { + let project_root = crate::states::PROJECT.read().project_path.clone(); + match crate::metadata::find_asset_by_uuid(&project_root, *uuid) { + Ok(entry) => { + if let crate::resource::ResourceReference::File(rel) = &entry.location { + let abs = project_root.join(rel); + let path_str = abs.to_string_lossy().to_string(); + // return early if already loaded + { + let engine_ref = ResourceReference::from_path(&abs).ok(); + if let Some(ref r) = engine_ref { + let registry = ASSET_REGISTRY.read(); + if let Some(h) = registry.get_texture_handle_by_reference(r) { + return Some(Ok(h)); + } + } + } + match std::fs::read(&abs) { + Ok(bytes) => { + let engine_ref = ResourceReference::from_path(&abs).ok(); + let mut texture = TextureBuilder::new(&graphics.device) + .with_bytes(graphics.clone(), bytes.as_slice()) + .label(path_str.as_str()) + .build(); + texture.reference = engine_ref; + let mut registry = ASSET_REGISTRY.write(); + Some(Ok(registry.add_texture_with_label(entry.name.clone(), texture))) + } + Err(e) => Some(Err(anyhow::anyhow!("Failed to read texture for UUID {}: {}", uuid, e))), + } + } else { + Some(Err(anyhow::anyhow!("Texture asset {} has no file-backed location", uuid))) + } + } + Err(e) => Some(Err(anyhow::anyhow!("UUID {} not found for texture: {}", uuid, e))), } - _ => None, - }, + } Some(TextureReference::RGBAColour(rgba)) => { let to_u8 = |v: f32| (v.clamp(0.0, 1.0) * 255.0).round() as u8; let mut registry = ASSET_REGISTRY.write(); @@ -745,64 +757,18 @@ impl Component for MeshRenderer { } fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> { - // let entity_label_raw = world - // .query_one::<&Label>(entity) - // .get() - // .map(|label| label.as_str().to_string()) - // .unwrap_or_else(|_| "unnamed_entity".to_string()); - // - // let sanitize_segment = |segment: &str| -> String { - // let mut result = String::with_capacity(segment.len()); - // for ch in segment.chars() { - // if ch.is_ascii_alphanumeric() { - // result.push(ch.to_ascii_lowercase()); - // } else if ch == '_' || ch == '-' { - // result.push(ch); - // } else { - // result.push('_'); - // } - // } - // - // let trimmed = result.trim_matches('_').to_string(); - // if trimmed.is_empty() { - // "unnamed_entity".to_string() - // } else { - // trimmed - // } - // }; - - // let proc_obj_type_name = |ty: &ProcObjType| -> &'static str { - // match ty { - // ProcObjType::Cuboid => "cuboid", - // } - // }; - // - // let entity_label = sanitize_segment(entity_label_raw.as_str()); - - let save_reference = |reference: ResourceReference, context: &str| -> ResourceReference { - match &reference { - ResourceReference::File(_) | ResourceReference::Procedural(_) => reference, - ResourceReference::Embedded(_) => { - match reference - .as_file(None) - .and_then(|path| ResourceReference::from_path(path.as_path())) - { - Ok(file_ref) => file_ref, - Err(err) => { - log::warn!( - "Failed to save {} as file-backed reference: {}", - context, - err - ); - ResourceReference::default() - } + let save_optional_texture_reference = |tex_reference: Option<ResourceReference>| -> Option<TextureReference> { + let resource = tex_reference?; + if let ResourceReference::File(rel) = &resource { + if !rel.is_empty() { + let project_root = crate::states::PROJECT.read().project_path.clone(); + let abs = project_root.join("resources").join(rel); + if let Ok(entry) = crate::metadata::generate_eucmeta(&abs, &project_root) { + return Some(TextureReference::AssetUuid(entry.uuid)); } } } - }; - - let save_optional_texture_reference = |reference: Option<ResourceReference>, context: &str| { - reference.map(|resource| TextureReference::Resource(save_reference(resource, context))) + None }; let asset = ASSET_REGISTRY.read(); @@ -848,10 +814,7 @@ impl Component for MeshRenderer { .get_texture(mat.diffuse_texture) .and_then(|t| t.reference.clone()) }; - let diffuse_texture = save_optional_texture_reference( - diffuse_texture, - "mesh renderer diffuse texture", - ); + let diffuse_texture = save_optional_texture_reference(diffuse_texture); let normal_texture = if default_material.map(|default| default.normal_texture) == Some(mat.normal_texture) @@ -861,10 +824,7 @@ impl Component for MeshRenderer { mat.normal_texture .and_then(|h| asset.get_texture(h).and_then(|t| t.reference.clone())) }; - let normal_texture = save_optional_texture_reference( - normal_texture, - "mesh renderer normal texture", - ); + let normal_texture = save_optional_texture_reference(normal_texture); let emissive_texture = if default_material.map(|default| default.emissive_texture) == Some(mat.emissive_texture) @@ -874,10 +834,7 @@ impl Component for MeshRenderer { mat.emissive_texture .and_then(|h| asset.get_texture(h).and_then(|t| t.reference.clone())) }; - let emissive_texture = save_optional_texture_reference( - emissive_texture, - "mesh renderer emissive texture", - ); + let emissive_texture = save_optional_texture_reference(emissive_texture); let occlusion_texture = if default_material.map(|default| default.occlusion_texture) == Some(mat.occlusion_texture) @@ -887,10 +844,7 @@ impl Component for MeshRenderer { mat.occlusion_texture .and_then(|h| asset.get_texture(h).and_then(|t| t.reference.clone())) }; - let occlusion_texture = save_optional_texture_reference( - occlusion_texture, - "mesh renderer occlusion texture", - ); + let occlusion_texture = save_optional_texture_reference(occlusion_texture); let metallic_roughness_texture = if default_material .map(|default| default.metallic_roughness_texture) @@ -901,10 +855,7 @@ impl Component for MeshRenderer { mat.metallic_roughness_texture .and_then(|h| asset.get_texture(h).and_then(|t| t.reference.clone())) }; - let metallic_roughness_texture = save_optional_texture_reference( - metallic_roughness_texture, - "mesh renderer metallic-roughness texture", - ); + let metallic_roughness_texture = save_optional_texture_reference(metallic_roughness_texture); texture_override.insert( label.to_string(), @@ -297,6 +297,8 @@ impl ProjectConfig { self.last_opened_scene = Some(first.scene_name.clone()); } + crate::metadata::scan_and_generate_eucmeta(&project_root); + Ok(()) } @@ -322,28 +324,6 @@ impl ProjectConfig { } } -/// The resource config. -#[derive(Default, Debug, Serialize, Deserialize)] -pub struct ResourceConfig { - /// The path to the resource folder. - pub path: PathBuf, - /// The files and folders of the assets - pub nodes: Vec<Node>, -} - -impl ResourceConfig { - /// Updates the in-memory ResourceConfig by re-scanning the resource directory. - pub fn update_mem(&mut self) -> anyhow::Result<ResourceConfig> { - let resource_dir = self.path.clone(); - let project_path = resource_dir.parent().unwrap_or(&resource_dir).to_path_buf(); - let updated_config = ResourceConfig { - path: resource_dir.clone(), - nodes: collect_nodes(&resource_dir, &project_path, vec!["thumbnails"].as_slice()), - }; - Ok(updated_config) - } -} - #[derive(Default, Debug, Serialize, Deserialize, Clone)] pub struct SourceConfig { /// The path to the resource folder. @@ -222,6 +222,43 @@ pub struct PackedAssetEntry { pub dependencies: Vec<UuidV4>, } +/// generates `.eucmeta` files for resources +pub fn scan_and_generate_eucmeta(project_root: &Path) -> usize { + let resources_dir = project_root.join("resources"); + let mut created = 0usize; + fn walk(dir: &Path, project_root: &Path, created: &mut usize) { + let Ok(read) = std::fs::read_dir(dir) else { return }; + for entry in read.flatten() { + let path = entry.path(); + if path.is_dir() { + walk(&path, project_root, created); + continue; + } + // skip sidecars themselves + if path.extension().and_then(|e| e.to_str()) == Some("eucmeta") { + continue; + } + // only generate for known asset types + if detect_asset_type(&path).is_none() { + continue; + } + let meta_path = PathBuf::from(format!("{}.eucmeta", path.display())); + if meta_path.exists() { + continue; + } + match generate_eucmeta(&path, project_root) { + Ok(_) => *created += 1, + Err(e) => log::warn!("Failed to generate .eucmeta for '{}': {}", path.display(), e), + } + } + } + walk(&resources_dir, project_root, &mut created); + if created > 0 { + log::info!("Generated {} .eucmeta sidecar(s) during project scan", created); + } + created +} + /// Scans all `.eucmeta` files under `<project_root>/resources/` and returns /// the [`AssetEntry`] whose UUID matches `uuid`. /// @@ -81,6 +81,10 @@ pub struct SceneSettings { /// Overlays all billboard ui for all entities if it exists as a component. #[serde(default = "SceneSettings::default_overlay_billboard")] pub overlay_billboard: bool, + + /// Controls the strength of ambient/IBL lighting for this scene. + #[serde(default = "SceneSettings::default_ambient_strength")] + pub ambient_strength: f32, } impl SceneSettings { @@ -91,6 +95,7 @@ impl SceneSettings { show_hitboxes: false, overlay_hud: false, overlay_billboard: true, + ambient_strength: 1.0, } } @@ -101,6 +106,10 @@ impl SceneSettings { pub(crate) const fn default_overlay_billboard() -> bool { true } + + pub(crate) const fn default_ambient_strength() -> f32 { + 1.0 + } } /// Specifies the configuration of a scene, such as its entities, hierarchies and any settings that @@ -8,8 +8,30 @@ use dropbear_engine::texture::{Texture, TextureWrapMode}; use dropbear_engine::utils::ResourceReference; use dropbear_engine::wgpu::util::DeviceExt; use dropbear_engine::wgpu; +use uuid::Uuid; -use crate::utils::ResolveReference; +use crate::uuid::UuidV4; + +/// How a texture is referenced inside a compiled model (`.eucmdl`). +/// +/// `AssetUuid` is the canonical form for any texture that lives on disk and has +/// a `.eucmeta` sidecar. `Embedded` is used for textures that were packed +/// directly inside a source file (e.g. GLTF-embedded data) and have not yet +/// been extracted as standalone assets. +#[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Debug, Clone, serde::Serialize, serde::Deserialize)] +pub enum EucalyptusTextureRef { + /// UUID of a file-backed texture tracked by a `.eucmeta` sidecar. + AssetUuid(UuidV4), + /// Raw image bytes embedded directly in the model file. + Embedded(Arc<[u8]>), +} + +impl EucalyptusTextureRef { + /// Constructs from a `uuid::Uuid`. + pub fn from_uuid(uuid: Uuid) -> Self { + Self::AssetUuid(UuidV4::from(uuid)) + } +} /// The serialized format for a Model without all the buffers and stuff. /// @@ -163,11 +185,11 @@ impl EucalyptusMesh { #[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Debug, serde::Serialize, serde::Deserialize)] pub struct EucalyptusMaterial { pub name: String, - pub diffuse_texture: ResourceReference, // the file can either be a eucalyptus texture or a standard file - pub normal_texture: Option<ResourceReference>, // same - pub emissive_texture: Option<ResourceReference>, // same - pub metallic_roughness_texture: Option<ResourceReference>, // same - pub occlusion_texture: Option<ResourceReference>, // same + pub diffuse_texture: Option<EucalyptusTextureRef>, + pub normal_texture: Option<EucalyptusTextureRef>, + pub emissive_texture: Option<EucalyptusTextureRef>, + pub metallic_roughness_texture: Option<EucalyptusTextureRef>, + pub occlusion_texture: Option<EucalyptusTextureRef>, pub tint: [f32; 4], pub emissive_factor: [f32; 3], pub metallic_factor: f32, @@ -184,19 +206,29 @@ pub struct EucalyptusMaterial { impl From<Material> for EucalyptusMaterial { fn from(value: Material) -> Self { - let get_texture = |tex: Option<Handle<Texture>>| -> Option<ResourceReference> { - if let Some(tex) = tex { - if let Some(t) = ASSET_REGISTRY.read().get_texture(tex) { - return t.reference.clone(); + let project_root = crate::states::PROJECT.read().project_path.clone(); + + let get_texture = |tex: Option<Handle<Texture>>| -> Option<EucalyptusTextureRef> { + let tex = tex?; + let registry = ASSET_REGISTRY.read(); + let t = registry.get_texture(tex)?; + match t.reference.clone()? { + ResourceReference::File(rel) if !rel.is_empty() => { + let abs = project_root.join("resources").join(&rel); + crate::metadata::generate_eucmeta(&abs, &project_root) + .ok() + .map(|entry| EucalyptusTextureRef::from_uuid(entry.uuid)) } + ResourceReference::Embedded(bytes) => { + Some(EucalyptusTextureRef::Embedded(bytes)) + } + _ => None, } - - None }; Self { name: value.name, - diffuse_texture: get_texture(Some(value.diffuse_texture)).unwrap_or_default(), + diffuse_texture: get_texture(Some(value.diffuse_texture)), normal_texture: get_texture(value.normal_texture), emissive_texture: get_texture(value.emissive_texture), metallic_roughness_texture: get_texture(value.metallic_roughness_texture), @@ -221,40 +253,58 @@ impl EucalyptusMaterial { fn load_texture( &self, graphics: Arc<SharedGraphicsContext>, - reference: &ResourceReference, + reference: &EucalyptusTextureRef, suffix: &str, ) -> Option<Handle<Texture>> { match reference { - ResourceReference::File(path) if !path.is_empty() => { - let path = reference.resolve().ok()?; - let bytes = std::fs::read(path).ok()?; - let label = format!("{}_{}", self.name, suffix); - let mut texture = dropbear_engine::texture::TextureBuilder::new(&graphics.device) - .with_bytes(graphics.clone(), bytes.as_slice()) - .label(label.as_str()) - .build(); - texture.reference = Some(reference.clone()); - - let mut registry = ASSET_REGISTRY.write(); - Some(registry.add_texture(texture)) + EucalyptusTextureRef::AssetUuid(uuid_v4) => { + let uuid = uuid_v4.as_uuid(); + let project_root = crate::states::PROJECT.read().project_path.clone(); + let entry = crate::metadata::find_asset_by_uuid(&project_root, uuid) + .map_err(|e| log::warn!("load_texture: UUID {} not found: {}", uuid, e)) + .ok()?; + if let crate::resource::ResourceReference::File(rel) = &entry.location { + let abs = project_root.join(rel); + // Dedup: return cached handle if already loaded. + if let Ok(engine_ref) = ResourceReference::from_path(&abs) { + let registry = ASSET_REGISTRY.read(); + if let Some(h) = registry.get_texture_handle_by_reference(&engine_ref) { + return Some(h); + } + } + let bytes = std::fs::read(&abs) + .map_err(|e| log::warn!("load_texture: failed to read '{}': {}", abs.display(), e)) + .ok()?; + let label = format!("{}_{}", self.name, suffix); + let engine_ref = ResourceReference::from_path(&abs).ok(); + let mut texture = dropbear_engine::texture::TextureBuilder::new(&graphics.device) + .with_bytes(graphics.clone(), bytes.as_slice()) + .label(label.as_str()) + .build(); + texture.reference = engine_ref; + let mut registry = ASSET_REGISTRY.write(); + Some(registry.add_texture_with_label(entry.name, texture)) + } else { + log::warn!("load_texture: UUID {} has no file-backed location", uuid); + None + } } - ResourceReference::Embedded(bytes) => { + EucalyptusTextureRef::Embedded(bytes) => { let label = format!("{}_{}", self.name, suffix); let texture = dropbear_engine::texture::TextureBuilder::new(&graphics.device) .with_bytes(graphics.clone(), bytes) .label(label.as_str()) .build(); - let mut registry = ASSET_REGISTRY.write(); Some(registry.add_texture(texture)) } - _ => None, } } fn load(&self, graphics: Arc<SharedGraphicsContext>) -> Material { let diffuse_texture = { - let maybe = self.load_texture(graphics.clone(), &self.diffuse_texture, "diffuse"); + let maybe = self.diffuse_texture.as_ref() + .and_then(|r| self.load_texture(graphics.clone(), r, "diffuse")); if let Some(handle) = maybe { handle } else { @@ -394,7 +394,7 @@ impl AsFile for ResourceReference { }], materials: vec![EucalyptusMaterial { name: "procedural_material".to_string(), - diffuse_texture: ResourceReference::default(), + diffuse_texture: None, normal_texture: None, emissive_texture: None, metallic_roughness_texture: None, @@ -17,6 +17,8 @@ use tokio::{fs as tokio_fs, process::Command, task}; /// /// Returns the path of the build directory pub fn build(project_config: PathBuf) -> anyhow::Result<PathBuf> { + // todo: remake this entire function + return Err(anyhow::anyhow!("Not implemented yet")); log::info!("Started project building"); // create a build directory let project_root = project_config @@ -101,6 +103,9 @@ fn copy_dir_recursive(src: &Path, dst: &Path) -> anyhow::Result<()> { if ty.is_dir() { copy_dir_recursive(&entry.path(), &dst.join(entry.file_name()))?; } else { + if entry.path().extension().and_then(|e| e.to_str()) == Some("eucmeta") { + continue; + } fs::copy(entry.path(), dst.join(entry.file_name()))?; } } @@ -9,7 +9,7 @@ use eucalyptus_core::utils::ResolveReference; use hecs::Entity; use log::{info, warn}; use std::hash::{Hash, Hasher}; -use std::{cmp::Ordering, fs, hash::DefaultHasher, io, path::Path}; +use std::{cmp::Ordering, fs, hash::DefaultHasher, io, path::{Path, PathBuf}}; use crate::editor::{ AssetDivision, AssetNodeInfo, AssetNodeKind, ComponentNodeSelection, DraggedAsset, @@ -1088,6 +1088,13 @@ impl<'a> EditorTabViewer<'a> { ); } else { info!("Renamed to {}", target_path.display()); + let old_meta = PathBuf::from(format!("{}.eucmeta", rename.original_path.display())); + if old_meta.exists() { + let new_meta = PathBuf::from(format!("{}.eucmeta", target_path.display())); + if let Err(e) = fs::rename(&old_meta, &new_meta) { + warn!("Failed to move .eucmeta sidecar '{}': {}", old_meta.display(), e); + } + } } } @@ -1107,6 +1114,14 @@ impl<'a> EditorTabViewer<'a> { warn!("Failed to delete '{}': {}", info.path.display(), err); } else { info!("Deleted {}", info.path.display()); + if !info.is_dir { + let meta = PathBuf::from(format!("{}.eucmeta", info.path.display())); + if meta.exists() { + if let Err(e) = fs::remove_file(&meta) { + warn!("Failed to remove .eucmeta sidecar '{}': {}", meta.display(), e); + } + } + } } } @@ -1206,6 +1221,15 @@ impl<'a> EditorTabViewer<'a> { warn!("Failed to move '{}': {}", source_info.path.display(), err); } else { info!("Moved to {}", target_path.display()); + if !source_info.is_dir { + let old_meta = PathBuf::from(format!("{}.eucmeta", source_info.path.display())); + if old_meta.exists() { + let new_meta = PathBuf::from(format!("{}.eucmeta", target_path.display())); + if let Err(e) = fs::rename(&old_meta, &new_meta) { + warn!("Failed to move .eucmeta sidecar '{}': {}", old_meta.display(), e); + } + } + } } } @@ -39,7 +39,7 @@ impl<'a> EditorTabViewer<'a> { self.world.spawn((label,)); ui.close(); } - ui.menu_button("Import Template", |ui| { + ui.menu_button("Import Template", |_| { }); }), @@ -479,6 +479,12 @@ impl Scene for Editor { if let Some(globals) = &mut self.shader_globals { puffin::profile_scope!("Fetching globals"); globals.set_num_lights(enabled_light_count); + if let Some(scene_name) = &self.current_scene_name { + let scenes = SCENES.read(); + if let Some(scene) = scenes.iter().find(|s| s.scene_name == *scene_name) { + globals.set_ambient_strength(scene.settings.ambient_strength); + } + } globals.write(&graphics.queue); } @@ -39,6 +39,14 @@ impl<'a> EditorTabViewer<'a> { scene.settings.overlay_hud = overlay_hud; } ui.label("Renders the HUD UI overlay on top of the viewport"); + + ui.separator(); + ui.label("Ambient Strength"); + let mut ambient = scene.settings.ambient_strength; + if ui.add(egui::Slider::new(&mut ambient, 0.0..=2.0).step_by(0.01)).changed() { + scene.settings.ambient_strength = ambient; + } + ui.label("Controls the intensity of ambient/IBL lighting"); } else { ui.label("Scene not found"); } @@ -690,6 +690,12 @@ impl Scene for PlayMode { if let Some(globals) = &mut self.shader_globals { puffin::profile_scope!("Fetching globals"); globals.set_num_lights(enabled_light_count); + if let Some(scene_name) = &self.current_scene { + let scenes = SCENES.read(); + if let Some(scene) = scenes.iter().find(|s| &s.scene_name == scene_name) { + globals.set_ambient_strength(scene.settings.ambient_strength); + } + } globals.write(&graphics.queue); }