use crate::ecs::World;
use crate::gfx::lighting_preview;
use concinnity_cook::authoring::registry::RegisteredType;
use concinnity_core::ecs::ComponentAsset;
use serde_json::{Map, Value};
use super::{Apply, RenderConfig, component};
pub(super) fn plan(
world: &World,
ct: RegisteredType,
name: &str,
args: &Map<String, Value>,
keys: &[String],
) -> Option<Apply> {
if !is_expressible(ct, keys) || !lighting_preview::is_available(world) {
return None;
}
let id = crate::ecs::asset_id::intern(name);
match component::bake(ct, id, args).ok()? {
ComponentAsset::DirectionalLight(light) => {
let entity = world
.resource::<concinnity_core::ecs::EntityByName>()?
.get(id)?;
Some(Apply::Sun { entity, light })
}
ComponentAsset::VolumetricFog(fog) => {
if fog.enabled && !lighting_preview::fog_pass_built(world) {
return None;
}
Some(Apply::RenderConfig(RenderConfig::Fog(fog)))
}
ComponentAsset::GraphicsConfig(config) => {
Some(Apply::RenderConfig(RenderConfig::Graphics(config)))
}
ComponentAsset::PostProcessConfig(config) => {
Some(Apply::RenderConfig(RenderConfig::Post(Box::new(config))))
}
_ => None,
}
}
fn is_expressible(ct: RegisteredType, keys: &[String]) -> bool {
match ct {
RegisteredType::DirectionalLight | RegisteredType::VolumetricFog => true,
RegisteredType::GraphicsConfig => {
live_keys(keys, lighting_preview::LIVE_GRAPHICS_CONFIG_FIELDS)
}
RegisteredType::PostProcessConfig => {
live_keys(keys, lighting_preview::LIVE_POST_PROCESS_FIELDS)
}
_ => false,
}
}
pub(super) fn commit(world: &mut World, config: RenderConfig) {
match config {
RenderConfig::Fog(fog) => {
lighting_preview::apply_fog(world, Some(&fog));
}
RenderConfig::Graphics(config) => {
lighting_preview::apply_graphics_config(world, &config);
}
RenderConfig::Post(config) => {
lighting_preview::apply_post_process_config(world, &config);
}
}
}
pub(super) fn commit_sun(
world: &mut World,
entity: crate::ecs::Entity,
light: crate::components::DirectionalLight,
) {
world.replace_component(entity, ComponentAsset::DirectionalLight(light));
let lights: Vec<crate::components::DirectionalLight> = world
.query::<crate::components::DirectionalLight>()
.cloned()
.collect();
lighting_preview::apply_directional_lights(world, &lights);
}
fn live_keys(keys: &[String], live: &[&str]) -> bool {
keys.iter().all(|k| live.contains(&k.as_str()))
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn keys(names: &[&str]) -> Vec<String> {
names.iter().map(|k| k.to_string()).collect()
}
#[test]
fn the_wholesale_types_take_any_field() {
assert!(is_expressible(
RegisteredType::DirectionalLight,
&keys(&["direction", "color", "intensity"]),
));
assert!(is_expressible(
RegisteredType::VolumetricFog,
&keys(&["enabled", "density"]),
));
assert!(!is_expressible(
RegisteredType::PointLight,
&keys(&["intensity"])
));
assert!(!is_expressible(RegisteredType::Sprite, &keys(&["width"])));
}
#[test]
fn a_graphics_config_edit_declines_the_load_time_knobs() {
assert!(is_expressible(
RegisteredType::GraphicsConfig,
&keys(&["shadow_distance", "shadow_cascades"]),
));
assert!(!is_expressible(
RegisteredType::GraphicsConfig,
&keys(&["shadow_map_size"]),
));
assert!(
!is_expressible(
RegisteredType::GraphicsConfig,
&keys(&["shadow_distance", "frames_in_flight"]),
),
"one knob the renderer cannot take declines the whole change"
);
}
#[test]
fn a_post_process_edit_declines_the_toggles() {
assert!(is_expressible(
RegisteredType::PostProcessConfig,
&keys(&["ambient_intensity", "exposure_ev", "ssgi_intensity"]),
));
assert!(!is_expressible(
RegisteredType::PostProcessConfig,
&keys(&["ssao"]),
));
assert!(!is_expressible(
RegisteredType::PostProcessConfig,
&keys(&["aa_mode"]),
));
}
#[test]
fn every_lighting_type_declines_without_a_renderer() {
let world = World::new();
assert!(!lighting_preview::is_available(&world));
for (ty, args, changed) in [
("DirectionalLight", json!({ "intensity": 2.0 }), "intensity"),
("VolumetricFog", json!({ "density": 0.1 }), "density"),
(
"GraphicsConfig",
json!({ "shadow_distance": 90 }),
"shadow_distance",
),
(
"PostProcessConfig",
json!({ "ambient_intensity": 0.5 }),
"ambient_intensity",
),
] {
let ct = RegisteredType::parse(ty).expect("a registered type");
assert!(
plan(
&world,
ct,
"lighting",
args.as_object().unwrap(),
&keys(&[changed]),
)
.is_none(),
"{ty} declines with no renderer"
);
}
}
#[test]
fn committing_a_sun_writes_the_component() {
use crate::components::DirectionalLight;
let mut world = World::new();
let entity = world.push(DirectionalLight::default());
let edited = DirectionalLight {
direction: [0.0, -1.0, 0.0],
color: [0.2, 0.4, 0.6],
intensity: 5.0,
};
commit_sun(&mut world, entity, edited);
let held = world
.query::<DirectionalLight>()
.next()
.expect("the light column still holds it");
assert_eq!(held.direction, edited.direction);
assert_eq!(held.color, edited.color);
assert_eq!(held.intensity, edited.intensity);
}
#[test]
fn live_keys_needs_every_key() {
let live = ["shadow_distance", "shadow_cascades"];
assert!(live_keys(&[], &live), "no change is trivially live");
assert!(live_keys(&keys(&["shadow_cascades"]), &live));
assert!(!live_keys(&keys(&["shadow_cascades", "vsync"]), &live));
}
}