nightshade 0.14.1

A cross-platform data-oriented game engine.
Documentation
use serde::{Deserialize, Serialize};

use crate::ecs::audio::components::AudioBus;
use crate::render::wgpu::texture_cache::{SamplerSettings, TextureUsage};

use super::asset_uuid::AssetUuid;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneAudioSource {
    #[serde(default)]
    pub audio_uuid: Option<AssetUuid>,
    #[serde(default)]
    pub audio_name: Option<String>,
    #[serde(default = "default_volume")]
    pub volume: f32,
    #[serde(default)]
    pub looping: bool,
    #[serde(default)]
    pub playing: bool,
    #[serde(default)]
    pub spatial: bool,
    #[serde(default)]
    pub reverb: bool,
    #[serde(default)]
    pub bus: AudioBus,
    #[serde(default = "default_min_distance")]
    pub min_distance: f32,
    #[serde(default = "default_max_distance")]
    pub max_distance: f32,
    #[serde(default)]
    pub reverb_zones: Vec<(String, f32)>,
    #[serde(default)]
    pub random_clips: Vec<String>,
    #[serde(default)]
    pub random_pick: bool,
}

fn default_volume() -> f32 {
    1.0
}

fn default_min_distance() -> f32 {
    1.0
}

fn default_max_distance() -> f32 {
    50.0
}

impl SceneAudioSource {
    pub fn from_name(name: impl Into<String>) -> Self {
        Self {
            audio_uuid: None,
            audio_name: Some(name.into()),
            volume: 1.0,
            looping: false,
            playing: false,
            spatial: false,
            reverb: false,
            bus: AudioBus::Sfx,
            min_distance: 1.0,
            max_distance: 50.0,
            reverb_zones: Vec::new(),
            random_clips: Vec::new(),
            random_pick: false,
        }
    }

    pub fn from_uuid(uuid: AssetUuid) -> Self {
        Self {
            audio_uuid: Some(uuid),
            audio_name: None,
            volume: 1.0,
            looping: false,
            playing: false,
            spatial: false,
            reverb: false,
            bus: AudioBus::Sfx,
            min_distance: 1.0,
            max_distance: 50.0,
            reverb_zones: Vec::new(),
            random_clips: Vec::new(),
            random_pick: false,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScenePrefabInstance {
    #[serde(default)]
    pub prefab_uuid: Option<AssetUuid>,
    #[serde(default)]
    pub prefab_path: Option<String>,
    #[serde(default)]
    pub prefab_name: Option<String>,
}

impl ScenePrefabInstance {
    pub fn from_uuid(uuid: AssetUuid) -> Self {
        Self {
            prefab_uuid: Some(uuid),
            prefab_path: None,
            prefab_name: None,
        }
    }

    pub fn from_path(path: impl Into<String>) -> Self {
        Self {
            prefab_uuid: None,
            prefab_path: Some(path.into()),
            prefab_name: None,
        }
    }

    pub fn from_name(name: impl Into<String>) -> Self {
        Self {
            prefab_uuid: None,
            prefab_path: None,
            prefab_name: Some(name.into()),
        }
    }
}

/// Texture data embedded in a scene file. Keeps the original texture
/// name so material references resolve against the runtime texture
/// cache after load. The usage and sampler are critical for correct
/// rendering — without them, normal maps would be uploaded as sRGB
/// (causing wrong lighting) and any non-default wrap/filter settings
/// would be lost.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmbeddedTexture {
    pub name: String,
    pub data: EmbeddedTextureData,
    #[serde(default = "default_embedded_usage")]
    pub usage: TextureUsage,
    #[serde(default)]
    pub sampler: SamplerSettings,
}

fn default_embedded_usage() -> TextureUsage {
    TextureUsage::Color
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EmbeddedTextureData {
    Rgba {
        #[serde(with = "base64_bytes")]
        rgba_data: Vec<u8>,
        width: u32,
        height: u32,
    },
    Png {
        #[serde(with = "base64_bytes")]
        png_data: Vec<u8>,
    },
}

mod base64_bytes {
    use base64::{Engine, engine::general_purpose::STANDARD};
    use serde::{Deserialize, Deserializer, Serialize, Serializer};

    pub fn serialize<S: Serializer>(data: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
        STANDARD.encode(data).serialize(serializer)
    }

    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
        let string = String::deserialize(deserializer)?;
        STANDARD.decode(&string).map_err(serde::de::Error::custom)
    }
}

impl EmbeddedTexture {
    pub fn from_rgba(name: impl Into<String>, rgba_data: Vec<u8>, width: u32, height: u32) -> Self {
        Self {
            name: name.into(),
            data: EmbeddedTextureData::Rgba {
                rgba_data,
                width,
                height,
            },
            usage: TextureUsage::Color,
            sampler: SamplerSettings::DEFAULT,
        }
    }

    pub fn from_png(name: impl Into<String>, png_data: Vec<u8>) -> Self {
        Self {
            name: name.into(),
            data: EmbeddedTextureData::Png { png_data },
            usage: TextureUsage::Color,
            sampler: SamplerSettings::DEFAULT,
        }
    }

    pub fn with_usage(mut self, usage: TextureUsage) -> Self {
        self.usage = usage;
        self
    }

    pub fn with_sampler(mut self, sampler: SamplerSettings) -> Self {
        self.sampler = sampler;
        self
    }

    pub fn to_rgba(&self) -> Option<(Vec<u8>, u32, u32)> {
        self.data.to_rgba()
    }
}

impl EmbeddedTextureData {
    pub fn to_rgba(&self) -> Option<(Vec<u8>, u32, u32)> {
        match self {
            Self::Rgba {
                rgba_data,
                width,
                height,
            } => Some((rgba_data.clone(), *width, *height)),
            Self::Png { png_data } => {
                if let Ok(img) = image::load_from_memory(png_data) {
                    let rgba = img.to_rgba8();
                    let (width, height) = rgba.dimensions();
                    Some((rgba.into_raw(), width, height))
                } else {
                    None
                }
            }
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmbeddedAudio {
    #[serde(with = "base64_bytes")]
    pub data: Vec<u8>,
}

impl EmbeddedAudio {
    pub fn from_bytes(data: Vec<u8>) -> Self {
        Self { data }
    }

    pub fn data(&self) -> &[u8] {
        &self.data
    }
}