use anyhow::Result;
use crate::sdf::Path;
use crate::usd::{Prim, Stage};
use crate::schemas::lux::tokens::{
API_LIGHT, A_COLOR, A_COLOR_TEMPERATURE, A_DIFFUSE, A_ENABLE_COLOR_TEMPERATURE, A_EXPOSURE, A_INTENSITY,
A_NORMALIZE, A_SPECULAR, REL_FILTERS,
};
use super::common::{author_input_bool, author_input_color3f, author_input_float, author_rel_targets};
pub fn apply_light_api<'s>(stage: &'s Stage, path: impl Into<Path>) -> Result<LightAuthor<'s>> {
let prim = stage.override_prim(path)?.add_applied_schema(API_LIGHT)?;
Ok(LightAuthor { prim })
}
pub struct LightAuthor<'s> {
prim: Prim<'s>,
}
impl<'s> LightAuthor<'s> {
pub fn into_prim(self) -> Prim<'s> {
self.prim
}
}
pub(crate) fn set_intensity(stage: &Stage, prim: &Path, value: f32) -> Result<()> {
author_input_float(stage, prim, A_INTENSITY, value)
}
pub(crate) fn set_exposure(stage: &Stage, prim: &Path, value: f32) -> Result<()> {
author_input_float(stage, prim, A_EXPOSURE, value)
}
pub(crate) fn set_diffuse(stage: &Stage, prim: &Path, value: f32) -> Result<()> {
author_input_float(stage, prim, A_DIFFUSE, value)
}
pub(crate) fn set_specular(stage: &Stage, prim: &Path, value: f32) -> Result<()> {
author_input_float(stage, prim, A_SPECULAR, value)
}
pub(crate) fn set_normalize(stage: &Stage, prim: &Path, value: bool) -> Result<()> {
author_input_bool(stage, prim, A_NORMALIZE, value)
}
pub(crate) fn set_color(stage: &Stage, prim: &Path, value: [f32; 3]) -> Result<()> {
author_input_color3f(stage, prim, A_COLOR, value)
}
pub(crate) fn set_enable_color_temperature(stage: &Stage, prim: &Path, value: bool) -> Result<()> {
author_input_bool(stage, prim, A_ENABLE_COLOR_TEMPERATURE, value)
}
pub(crate) fn set_color_temperature(stage: &Stage, prim: &Path, value: f32) -> Result<()> {
author_input_float(stage, prim, A_COLOR_TEMPERATURE, value)
}
pub(crate) fn set_filters<I, P>(stage: &Stage, prim: &Path, targets: I) -> Result<()>
where
I: IntoIterator<Item = P>,
P: Into<Path>,
{
author_rel_targets(stage, prim, REL_FILTERS, targets)
}
pub trait LightApiSetters<'s>: Sized {
fn prim(&self) -> &Prim<'s>;
fn set_intensity(self, value: f32) -> Result<Self> {
set_intensity(self.prim().stage(), self.prim().path(), value)?;
Ok(self)
}
fn set_exposure(self, value: f32) -> Result<Self> {
set_exposure(self.prim().stage(), self.prim().path(), value)?;
Ok(self)
}
fn set_diffuse(self, value: f32) -> Result<Self> {
set_diffuse(self.prim().stage(), self.prim().path(), value)?;
Ok(self)
}
fn set_specular(self, value: f32) -> Result<Self> {
set_specular(self.prim().stage(), self.prim().path(), value)?;
Ok(self)
}
fn set_normalize(self, value: bool) -> Result<Self> {
set_normalize(self.prim().stage(), self.prim().path(), value)?;
Ok(self)
}
fn set_color(self, value: [f32; 3]) -> Result<Self> {
set_color(self.prim().stage(), self.prim().path(), value)?;
Ok(self)
}
fn set_enable_color_temperature(self, value: bool) -> Result<Self> {
set_enable_color_temperature(self.prim().stage(), self.prim().path(), value)?;
Ok(self)
}
fn set_color_temperature(self, value: f32) -> Result<Self> {
set_color_temperature(self.prim().stage(), self.prim().path(), value)?;
Ok(self)
}
fn set_filters<I, P>(self, targets: I) -> Result<Self>
where
I: IntoIterator<Item = P>,
P: Into<Path>,
{
set_filters(self.prim().stage(), self.prim().path(), targets)?;
Ok(self)
}
}
impl<'s> LightApiSetters<'s> for LightAuthor<'s> {
fn prim(&self) -> &Prim<'s> {
&self.prim
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sdf;
#[test]
fn apply_light_api_adds_api_and_writes_inputs() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
stage.define_prim("/Emitter")?.set_type_name("Mesh")?;
apply_light_api(&stage, sdf::path("/Emitter")?)?
.set_intensity(1500.0)?
.set_exposure(1.5)?
.set_diffuse(0.8)?
.set_specular(1.2)?
.set_normalize(true)?
.set_color([1.0, 0.9, 0.8])?
.set_enable_color_temperature(true)?
.set_color_temperature(5500.0)?;
let api = stage.api_schemas(&sdf::path("/Emitter")?)?;
assert!(api.iter().any(|s| s == "LightAPI"));
match stage.field::<sdf::Value>("/Emitter.inputs:intensity", sdf::FieldKey::Default)? {
Some(sdf::Value::Float(f)) => assert!((f - 1500.0).abs() < 1e-3),
other => panic!("expected Float(1500.0) for intensity, got {other:?}"),
}
match stage.field::<sdf::Value>("/Emitter.inputs:color", sdf::FieldKey::Default)? {
Some(sdf::Value::Vec3f(v)) => assert_eq!(v, [1.0, 0.9, 0.8]),
other => panic!("expected Vec3f for color, got {other:?}"),
}
match stage.field::<sdf::Value>("/Emitter.inputs:enableColorTemperature", sdf::FieldKey::Default)? {
Some(sdf::Value::Bool(b)) => assert!(b),
other => panic!("expected Bool(true) for enableColorTemperature, got {other:?}"),
}
Ok(())
}
#[test]
fn set_filters_writes_relationship_targets() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
stage.define_prim("/Emitter")?.set_type_name("Mesh")?;
stage.define_prim("/Filter1")?.set_type_name("LightFilter")?;
stage.define_prim("/Filter2")?.set_type_name("LightFilter")?;
apply_light_api(&stage, sdf::path("/Emitter")?)?
.set_filters([sdf::path("/Filter1")?, sdf::path("/Filter2")?])?;
let light = crate::schemas::lux::read_light_api(&stage, &sdf::path("/Emitter")?)?.expect("LightAPI applied");
assert_eq!(light.filters, vec!["/Filter1".to_string(), "/Filter2".to_string()],);
Ok(())
}
#[test]
fn round_trips_through_lightapi_reader() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
stage.define_prim("/Sun")?.set_type_name("Mesh")?;
apply_light_api(&stage, sdf::path("/Sun")?)?
.set_intensity(2000.0)?
.set_color([1.0, 0.95, 0.85])?
.set_normalize(true)?;
let light = crate::schemas::lux::read_light_api(&stage, &sdf::path("/Sun")?)?.expect("LightAPI applied");
assert!((light.intensity - 2000.0).abs() < 1e-3);
assert!((light.color[1] - 0.95).abs() < 1e-3);
assert!(light.normalize);
assert!((light.exposure - 0.0).abs() < 1e-6);
assert!((light.diffuse - 1.0).abs() < 1e-6);
Ok(())
}
}