use anyhow::Result;
use crate::sdf::Path;
use crate::usd::{Prim, Stage};
use crate::schemas::lux::tokens::{
A_HEIGHT, A_LENGTH, A_RADIUS, A_TEXTURE_FILE, A_WIDTH, T_CYLINDER_LIGHT, T_DISK_LIGHT, T_PORTAL_LIGHT,
T_RECT_LIGHT, T_SPHERE_LIGHT,
};
use super::common::{author_input_asset, author_input_float, author_treat_as_line, author_treat_as_point};
use super::light_api::LightApiSetters;
pub fn define_disk_light<'s>(stage: &'s Stage, path: impl Into<Path>) -> Result<DiskLightAuthor<'s>> {
let prim = stage.define_prim(path)?.set_type_name(T_DISK_LIGHT)?;
Ok(DiskLightAuthor { prim })
}
pub struct DiskLightAuthor<'s> {
prim: Prim<'s>,
}
impl<'s> DiskLightAuthor<'s> {
pub fn into_prim(self) -> Prim<'s> {
self.prim
}
pub fn set_radius(self, radius: f32) -> Result<Self> {
author_input_float(self.prim.stage(), self.prim.path(), A_RADIUS, radius)?;
Ok(self)
}
}
impl<'s> LightApiSetters<'s> for DiskLightAuthor<'s> {
fn prim(&self) -> &Prim<'s> {
&self.prim
}
}
pub fn define_rect_light<'s>(stage: &'s Stage, path: impl Into<Path>) -> Result<RectLightAuthor<'s>> {
let prim = stage.define_prim(path)?.set_type_name(T_RECT_LIGHT)?;
Ok(RectLightAuthor { prim })
}
pub struct RectLightAuthor<'s> {
prim: Prim<'s>,
}
impl<'s> RectLightAuthor<'s> {
pub fn into_prim(self) -> Prim<'s> {
self.prim
}
pub fn set_width(self, width: f32) -> Result<Self> {
author_input_float(self.prim.stage(), self.prim.path(), A_WIDTH, width)?;
Ok(self)
}
pub fn set_height(self, height: f32) -> Result<Self> {
author_input_float(self.prim.stage(), self.prim.path(), A_HEIGHT, height)?;
Ok(self)
}
pub fn set_texture_file(self, path: impl Into<String>) -> Result<Self> {
author_input_asset(self.prim.stage(), self.prim.path(), A_TEXTURE_FILE, path)?;
Ok(self)
}
}
impl<'s> LightApiSetters<'s> for RectLightAuthor<'s> {
fn prim(&self) -> &Prim<'s> {
&self.prim
}
}
pub fn define_sphere_light<'s>(stage: &'s Stage, path: impl Into<Path>) -> Result<SphereLightAuthor<'s>> {
let prim = stage.define_prim(path)?.set_type_name(T_SPHERE_LIGHT)?;
Ok(SphereLightAuthor { prim })
}
pub struct SphereLightAuthor<'s> {
prim: Prim<'s>,
}
impl<'s> SphereLightAuthor<'s> {
pub fn into_prim(self) -> Prim<'s> {
self.prim
}
pub fn set_radius(self, radius: f32) -> Result<Self> {
author_input_float(self.prim.stage(), self.prim.path(), A_RADIUS, radius)?;
Ok(self)
}
pub fn set_treat_as_point(self, treat_as_point: bool) -> Result<Self> {
author_treat_as_point(self.prim.stage(), self.prim.path(), treat_as_point)?;
Ok(self)
}
}
impl<'s> LightApiSetters<'s> for SphereLightAuthor<'s> {
fn prim(&self) -> &Prim<'s> {
&self.prim
}
}
pub fn define_cylinder_light<'s>(stage: &'s Stage, path: impl Into<Path>) -> Result<CylinderLightAuthor<'s>> {
let prim = stage.define_prim(path)?.set_type_name(T_CYLINDER_LIGHT)?;
Ok(CylinderLightAuthor { prim })
}
pub struct CylinderLightAuthor<'s> {
prim: Prim<'s>,
}
impl<'s> CylinderLightAuthor<'s> {
pub fn into_prim(self) -> Prim<'s> {
self.prim
}
pub fn set_length(self, length: f32) -> Result<Self> {
author_input_float(self.prim.stage(), self.prim.path(), A_LENGTH, length)?;
Ok(self)
}
pub fn set_radius(self, radius: f32) -> Result<Self> {
author_input_float(self.prim.stage(), self.prim.path(), A_RADIUS, radius)?;
Ok(self)
}
pub fn set_treat_as_line(self, treat_as_line: bool) -> Result<Self> {
author_treat_as_line(self.prim.stage(), self.prim.path(), treat_as_line)?;
Ok(self)
}
}
impl<'s> LightApiSetters<'s> for CylinderLightAuthor<'s> {
fn prim(&self) -> &Prim<'s> {
&self.prim
}
}
pub fn define_portal_light<'s>(stage: &'s Stage, path: impl Into<Path>) -> Result<PortalLightAuthor<'s>> {
let prim = stage.define_prim(path)?.set_type_name(T_PORTAL_LIGHT)?;
Ok(PortalLightAuthor { prim })
}
pub struct PortalLightAuthor<'s> {
prim: Prim<'s>,
}
impl<'s> PortalLightAuthor<'s> {
pub fn into_prim(self) -> Prim<'s> {
self.prim
}
pub fn set_width(self, width: f32) -> Result<Self> {
author_input_float(self.prim.stage(), self.prim.path(), A_WIDTH, width)?;
Ok(self)
}
pub fn set_height(self, height: f32) -> Result<Self> {
author_input_float(self.prim.stage(), self.prim.path(), A_HEIGHT, height)?;
Ok(self)
}
}
impl<'s> LightApiSetters<'s> for PortalLightAuthor<'s> {
fn prim(&self) -> &Prim<'s> {
&self.prim
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sdf;
#[test]
fn disk_light_roundtrip() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
define_disk_light(&stage, sdf::path("/Disk")?)?
.set_radius(0.75)?
.set_intensity(600.0)?
.set_color([1.0, 0.5, 0.5])?;
let light = crate::schemas::lux::read_disk_light(&stage, &sdf::path("/Disk")?)?.expect("DiskLight");
assert!((light.radius - 0.75).abs() < 1e-3);
assert!((light.common.intensity - 600.0).abs() < 1e-3);
assert_eq!(light.common.color, [1.0, 0.5, 0.5]);
Ok(())
}
#[test]
fn rect_light_roundtrip_with_texture() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
define_rect_light(&stage, sdf::path("/Rect")?)?
.set_width(2.0)?
.set_height(1.0)?
.set_texture_file("./softbox.exr")?
.set_intensity(1500.0)?;
let light = crate::schemas::lux::read_rect_light(&stage, &sdf::path("/Rect")?)?.expect("RectLight");
assert!((light.width - 2.0).abs() < 1e-3);
assert!((light.height - 1.0).abs() < 1e-3);
assert_eq!(light.texture_file.as_deref(), Some("./softbox.exr"));
assert!((light.common.intensity - 1500.0).abs() < 1e-3);
Ok(())
}
#[test]
fn sphere_light_treat_as_point_flag() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
define_sphere_light(&stage, sdf::path("/Bulb")?)?
.set_radius(0.1)?
.set_treat_as_point(true)?
.set_intensity(800.0)?;
let light = crate::schemas::lux::read_sphere_light(&stage, &sdf::path("/Bulb")?)?.expect("SphereLight");
assert!((light.radius - 0.1).abs() < 1e-3);
assert!(light.treat_as_point);
Ok(())
}
#[test]
fn cylinder_light_treat_as_line_flag() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
define_cylinder_light(&stage, sdf::path("/Tube")?)?
.set_length(3.0)?
.set_radius(0.05)?
.set_treat_as_line(true)?;
let light = crate::schemas::lux::read_cylinder_light(&stage, &sdf::path("/Tube")?)?.expect("CylinderLight");
assert!((light.length - 3.0).abs() < 1e-3);
assert!((light.radius - 0.05).abs() < 1e-3);
assert!(light.treat_as_line);
Ok(())
}
#[test]
fn portal_light_dimensions() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
define_portal_light(&stage, sdf::path("/Portal")?)?
.set_width(1.2)?
.set_height(2.4)?;
let portal = crate::schemas::lux::read_portal_light(&stage, &sdf::path("/Portal")?)?.expect("PortalLight");
assert!((portal.width - 1.2).abs() < 1e-3);
assert!((portal.height - 2.4).abs() < 1e-3);
Ok(())
}
}