use anyhow::Result;
use crate::sdf;
use crate::usd::{Attribute, Prim, Relationship, Stage};
use super::tokens as tok;
use super::{impl_lux_schema, Light};
use crate::schemas::common::{get_typed, get_typed_any, get_with_api};
#[derive(Clone, derive_more::Deref)]
pub struct SphereLight(Prim);
impl SphereLight {
pub fn define(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Self> {
Ok(Self(stage.define_prim(path)?.set_type_name(tok::T_SPHERE_LIGHT)?))
}
pub fn get(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Option<Self>> {
get_typed(stage, path, tok::T_SPHERE_LIGHT).map(|o| o.map(Self))
}
pub fn radius_attr(&self) -> Attribute {
self.attribute(tok::A_RADIUS)
}
pub fn create_radius_attr(&self) -> Result<Attribute> {
Ok(self.create_attribute(tok::A_RADIUS, "float")?.set_custom(false)?)
}
pub fn treat_as_point_attr(&self) -> Attribute {
self.attribute(tok::A_TREAT_AS_POINT)
}
pub fn create_treat_as_point_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_TREAT_AS_POINT, "bool")?
.set_custom(false)?)
}
}
impl_lux_schema!(boundable_light SphereLight);
#[derive(Clone, derive_more::Deref)]
pub struct DiskLight(Prim);
impl DiskLight {
pub fn define(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Self> {
Ok(Self(stage.define_prim(path)?.set_type_name(tok::T_DISK_LIGHT)?))
}
pub fn get(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Option<Self>> {
get_typed(stage, path, tok::T_DISK_LIGHT).map(|o| o.map(Self))
}
pub fn radius_attr(&self) -> Attribute {
self.attribute(tok::A_RADIUS)
}
pub fn create_radius_attr(&self) -> Result<Attribute> {
Ok(self.create_attribute(tok::A_RADIUS, "float")?.set_custom(false)?)
}
}
impl_lux_schema!(boundable_light DiskLight);
#[derive(Clone, derive_more::Deref)]
pub struct RectLight(Prim);
impl RectLight {
pub fn define(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Self> {
Ok(Self(stage.define_prim(path)?.set_type_name(tok::T_RECT_LIGHT)?))
}
pub fn get(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Option<Self>> {
get_typed(stage, path, tok::T_RECT_LIGHT).map(|o| o.map(Self))
}
pub fn width_attr(&self) -> Attribute {
self.attribute(tok::A_WIDTH)
}
pub fn create_width_attr(&self) -> Result<Attribute> {
Ok(self.create_attribute(tok::A_WIDTH, "float")?.set_custom(false)?)
}
pub fn height_attr(&self) -> Attribute {
self.attribute(tok::A_HEIGHT)
}
pub fn create_height_attr(&self) -> Result<Attribute> {
Ok(self.create_attribute(tok::A_HEIGHT, "float")?.set_custom(false)?)
}
pub fn texture_file_attr(&self) -> Attribute {
self.attribute(tok::A_TEXTURE_FILE)
}
pub fn create_texture_file_attr(&self) -> Result<Attribute> {
Ok(self.create_attribute(tok::A_TEXTURE_FILE, "asset")?.set_custom(false)?)
}
}
impl_lux_schema!(boundable_light RectLight);
#[derive(Clone, derive_more::Deref)]
pub struct CylinderLight(Prim);
impl CylinderLight {
pub fn define(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Self> {
Ok(Self(stage.define_prim(path)?.set_type_name(tok::T_CYLINDER_LIGHT)?))
}
pub fn get(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Option<Self>> {
get_typed(stage, path, tok::T_CYLINDER_LIGHT).map(|o| o.map(Self))
}
pub fn length_attr(&self) -> Attribute {
self.attribute(tok::A_LENGTH)
}
pub fn create_length_attr(&self) -> Result<Attribute> {
Ok(self.create_attribute(tok::A_LENGTH, "float")?.set_custom(false)?)
}
pub fn radius_attr(&self) -> Attribute {
self.attribute(tok::A_RADIUS)
}
pub fn create_radius_attr(&self) -> Result<Attribute> {
Ok(self.create_attribute(tok::A_RADIUS, "float")?.set_custom(false)?)
}
pub fn treat_as_line_attr(&self) -> Attribute {
self.attribute(tok::A_TREAT_AS_LINE)
}
pub fn create_treat_as_line_attr(&self) -> Result<Attribute> {
Ok(self.create_attribute(tok::A_TREAT_AS_LINE, "bool")?.set_custom(false)?)
}
}
impl_lux_schema!(boundable_light CylinderLight);
#[derive(Clone, derive_more::Deref)]
pub struct PortalLight(Prim);
impl PortalLight {
pub fn define(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Self> {
Ok(Self(stage.define_prim(path)?.set_type_name(tok::T_PORTAL_LIGHT)?))
}
pub fn get(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Option<Self>> {
get_typed(stage, path, tok::T_PORTAL_LIGHT).map(|o| o.map(Self))
}
pub fn width_attr(&self) -> Attribute {
self.attribute(tok::A_WIDTH)
}
pub fn create_width_attr(&self) -> Result<Attribute> {
Ok(self.create_attribute(tok::A_WIDTH, "float")?.set_custom(false)?)
}
pub fn height_attr(&self) -> Attribute {
self.attribute(tok::A_HEIGHT)
}
pub fn create_height_attr(&self) -> Result<Attribute> {
Ok(self.create_attribute(tok::A_HEIGHT, "float")?.set_custom(false)?)
}
}
impl_lux_schema!(boundable_light PortalLight);
#[derive(Clone, derive_more::Deref)]
pub struct DistantLight(Prim);
impl DistantLight {
pub fn define(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Self> {
Ok(Self(stage.define_prim(path)?.set_type_name(tok::T_DISTANT_LIGHT)?))
}
pub fn get(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Option<Self>> {
get_typed(stage, path, tok::T_DISTANT_LIGHT).map(|o| o.map(Self))
}
pub fn angle_attr(&self) -> Attribute {
self.attribute(tok::A_ANGLE)
}
pub fn create_angle_attr(&self) -> Result<Attribute> {
Ok(self.create_attribute(tok::A_ANGLE, "float")?.set_custom(false)?)
}
}
impl_lux_schema!(nonboundable_light DistantLight);
#[derive(Clone, derive_more::Deref)]
pub struct GeometryLight(Prim);
impl GeometryLight {
pub fn define(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Self> {
Ok(Self(stage.define_prim(path)?.set_type_name(tok::T_GEOMETRY_LIGHT)?))
}
pub fn get(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Option<Self>> {
get_typed(stage, path, tok::T_GEOMETRY_LIGHT).map(|o| o.map(Self))
}
pub fn geometry_rel(&self) -> Relationship {
self.relationship(tok::REL_GEOMETRY)
}
pub fn create_geometry_rel(&self) -> Result<Relationship> {
Ok(self.create_relationship(tok::REL_GEOMETRY)?.set_custom(false)?)
}
}
impl_lux_schema!(nonboundable_light GeometryLight);
#[derive(Clone, derive_more::Deref)]
pub struct DomeLight(Prim);
impl DomeLight {
pub fn define(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Self> {
Ok(Self(stage.define_prim(path)?.set_type_name(tok::T_DOME_LIGHT)?))
}
pub fn define_v1(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Self> {
Ok(Self(stage.define_prim(path)?.set_type_name(tok::T_DOME_LIGHT_1)?))
}
pub fn get(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Option<Self>> {
get_typed_any(stage, path, &[tok::T_DOME_LIGHT, tok::T_DOME_LIGHT_1]).map(|o| o.map(Self))
}
pub fn texture_file_attr(&self) -> Attribute {
self.attribute(tok::A_TEXTURE_FILE)
}
pub fn create_texture_file_attr(&self) -> Result<Attribute> {
Ok(self.create_attribute(tok::A_TEXTURE_FILE, "asset")?.set_custom(false)?)
}
pub fn texture_format_attr(&self) -> Attribute {
self.attribute(tok::A_TEXTURE_FORMAT)
}
pub fn create_texture_format_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_TEXTURE_FORMAT, "token")?
.set_custom(false)?)
}
pub fn guide_radius_attr(&self) -> Attribute {
self.attribute(tok::A_GUIDE_RADIUS)
}
pub fn create_guide_radius_attr(&self) -> Result<Attribute> {
Ok(self.create_attribute(tok::A_GUIDE_RADIUS, "float")?.set_custom(false)?)
}
pub fn pole_axis_attr(&self) -> Attribute {
self.attribute(tok::A_POLE_AXIS)
}
pub fn create_pole_axis_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_POLE_AXIS, "token")?
.set_custom(false)?
.set_variability(sdf::Variability::Uniform)?)
}
pub fn portals_rel(&self) -> Relationship {
self.relationship(tok::REL_PORTALS)
}
pub fn create_portals_rel(&self) -> Result<Relationship> {
Ok(self.create_relationship(tok::REL_PORTALS)?.set_custom(false)?)
}
}
impl_lux_schema!(nonboundable_light DomeLight);
#[derive(Clone, derive_more::Deref)]
pub struct LightFilter(Prim);
impl LightFilter {
pub fn define(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Self> {
Ok(Self(stage.define_prim(path)?.set_type_name(tok::T_LIGHT_FILTER)?))
}
pub fn get(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Option<Self>> {
get_typed(stage, path, tok::T_LIGHT_FILTER).map(|o| o.map(Self))
}
}
impl_lux_schema!(xformable LightFilter);
#[derive(Clone, derive_more::Deref)]
pub struct LightAPI(Prim);
impl LightAPI {
pub fn apply(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Self> {
Ok(Self(stage.override_prim(path)?.add_applied_schema(tok::API_LIGHT)?))
}
pub fn get(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Option<Self>> {
get_with_api(
stage,
path,
&[tok::API_LIGHT, tok::API_MESH_LIGHT, tok::API_VOLUME_LIGHT],
)
.map(|o| o.map(Self))
}
}
impl_lux_schema!(applied_api LightAPI);
impl Light for LightAPI {}
#[derive(Clone, derive_more::Deref)]
pub struct ShapingAPI(Prim);
impl ShapingAPI {
pub fn apply(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Self> {
Ok(Self(stage.override_prim(path)?.add_applied_schema(tok::API_SHAPING)?))
}
pub fn get(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Option<Self>> {
get_with_api(stage, path, &[tok::API_SHAPING]).map(|o| o.map(Self))
}
pub fn focus_attr(&self) -> Attribute {
self.attribute(tok::A_SHAPING_FOCUS)
}
pub fn create_focus_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_SHAPING_FOCUS, "float")?
.set_custom(false)?)
}
pub fn focus_tint_attr(&self) -> Attribute {
self.attribute(tok::A_SHAPING_FOCUS_TINT)
}
pub fn create_focus_tint_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_SHAPING_FOCUS_TINT, "color3f")?
.set_custom(false)?)
}
pub fn cone_angle_attr(&self) -> Attribute {
self.attribute(tok::A_SHAPING_CONE_ANGLE)
}
pub fn create_cone_angle_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_SHAPING_CONE_ANGLE, "float")?
.set_custom(false)?)
}
pub fn cone_softness_attr(&self) -> Attribute {
self.attribute(tok::A_SHAPING_CONE_SOFTNESS)
}
pub fn create_cone_softness_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_SHAPING_CONE_SOFTNESS, "float")?
.set_custom(false)?)
}
pub fn ies_file_attr(&self) -> Attribute {
self.attribute(tok::A_SHAPING_IES_FILE)
}
pub fn create_ies_file_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_SHAPING_IES_FILE, "asset")?
.set_custom(false)?)
}
pub fn ies_angle_scale_attr(&self) -> Attribute {
self.attribute(tok::A_SHAPING_IES_ANGLE_SCALE)
}
pub fn create_ies_angle_scale_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_SHAPING_IES_ANGLE_SCALE, "float")?
.set_custom(false)?)
}
pub fn ies_normalize_attr(&self) -> Attribute {
self.attribute(tok::A_SHAPING_IES_NORMALIZE)
}
pub fn create_ies_normalize_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_SHAPING_IES_NORMALIZE, "bool")?
.set_custom(false)?)
}
}
impl_lux_schema!(applied_api ShapingAPI);
#[derive(Clone, derive_more::Deref)]
pub struct ShadowAPI(Prim);
impl ShadowAPI {
pub fn apply(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Self> {
Ok(Self(stage.override_prim(path)?.add_applied_schema(tok::API_SHADOW)?))
}
pub fn get(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Option<Self>> {
get_with_api(stage, path, &[tok::API_SHADOW]).map(|o| o.map(Self))
}
pub fn enable_attr(&self) -> Attribute {
self.attribute(tok::A_SHADOW_ENABLE)
}
pub fn create_enable_attr(&self) -> Result<Attribute> {
Ok(self.create_attribute(tok::A_SHADOW_ENABLE, "bool")?.set_custom(false)?)
}
pub fn color_attr(&self) -> Attribute {
self.attribute(tok::A_SHADOW_COLOR)
}
pub fn create_color_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_SHADOW_COLOR, "color3f")?
.set_custom(false)?)
}
pub fn distance_attr(&self) -> Attribute {
self.attribute(tok::A_SHADOW_DISTANCE)
}
pub fn create_distance_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_SHADOW_DISTANCE, "float")?
.set_custom(false)?)
}
pub fn falloff_attr(&self) -> Attribute {
self.attribute(tok::A_SHADOW_FALLOFF)
}
pub fn create_falloff_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_SHADOW_FALLOFF, "float")?
.set_custom(false)?)
}
pub fn falloff_gamma_attr(&self) -> Attribute {
self.attribute(tok::A_SHADOW_FALLOFF_GAMMA)
}
pub fn create_falloff_gamma_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_SHADOW_FALLOFF_GAMMA, "float")?
.set_custom(false)?)
}
}
impl_lux_schema!(applied_api ShadowAPI);
#[derive(Clone, derive_more::Deref)]
pub struct LightListAPI(Prim);
impl LightListAPI {
pub fn apply(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Self> {
Ok(Self(
stage.override_prim(path)?.add_applied_schema(tok::API_LIGHT_LIST)?,
))
}
pub fn get(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Option<Self>> {
get_with_api(stage, path, &[tok::API_LIGHT_LIST]).map(|o| o.map(Self))
}
pub fn light_list_rel(&self) -> Relationship {
self.relationship(tok::REL_LIGHT_LIST)
}
pub fn create_light_list_rel(&self) -> Result<Relationship> {
Ok(self.create_relationship(tok::REL_LIGHT_LIST)?.set_custom(false)?)
}
pub fn cache_behavior_attr(&self) -> Attribute {
self.attribute(tok::A_LIGHT_LIST_CACHE_BEHAVIOR)
}
pub fn create_cache_behavior_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_LIGHT_LIST_CACHE_BEHAVIOR, "token")?
.set_custom(false)?
.set_variability(sdf::Variability::Uniform)?)
}
}
impl_lux_schema!(applied_api LightListAPI);
#[cfg(test)]
mod tests {
use super::*;
use crate::schemas::geom::Xformable;
use crate::usd::SchemaBase;
#[test]
fn sphere_light_roundtrip() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
let s = SphereLight::define(&stage, "/Bulb")?;
s.create_radius_attr()?.set(0.25_f32)?;
s.create_treat_as_point_attr()?.set(true)?;
s.create_intensity_attr()?.set(800.0_f32)?;
let s = SphereLight::get(&stage, "/Bulb")?.expect("SphereLight");
assert_eq!(s.radius_attr().get()?, Some(sdf::Value::Float(0.25)));
assert_eq!(s.treat_as_point_attr().get()?, Some(sdf::Value::Bool(true)));
assert_eq!(s.intensity_attr().get()?, Some(sdf::Value::Float(800.0)));
assert!(SphereLight::get(&stage, "/Missing")?.is_none());
Ok(())
}
#[test]
fn rect_light_texture_and_filters() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
let r = RectLight::define(&stage, "/Rect")?;
r.create_width_attr()?.set(2.0_f32)?;
r.create_texture_file_attr()?
.set(sdf::Value::AssetPath("./softbox.exr".into()))?;
r.create_filters_rel()?.set_targets([sdf::path("/Filter")?])?;
let r = RectLight::get(&stage, "/Rect")?.expect("RectLight");
assert_eq!(r.width_attr().get()?, Some(sdf::Value::Float(2.0)));
assert_eq!(r.filters_rel().targets()?, vec![sdf::path("/Filter")?]);
Ok(())
}
#[test]
fn distant_light_unauthored_intensity_is_none() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
let d = DistantLight::define(&stage, "/Sun")?;
d.create_angle_attr()?.set(0.53_f32)?;
assert_eq!(d.angle_attr().get()?, Some(sdf::Value::Float(0.53)));
assert_eq!(d.intensity_attr().get::<sdf::Value>()?, None);
Ok(())
}
#[test]
fn geometry_light_links_mesh() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
stage.define_prim("/Emitter")?.set_type_name("Mesh")?;
let g = GeometryLight::define(&stage, "/Light")?;
g.create_geometry_rel()?.set_targets([sdf::path("/Emitter")?])?;
let g = GeometryLight::get(&stage, "/Light")?.expect("GeometryLight");
assert_eq!(g.geometry_rel().targets()?, vec![sdf::path("/Emitter")?]);
Ok(())
}
#[test]
fn dome_light_and_v1() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
let d = DomeLight::define(&stage, "/Dome")?;
d.create_texture_format_attr()?
.set(sdf::Value::Token("latlong".into()))?;
d.create_portals_rel()?.set_targets([sdf::path("/Dome/Portal")?])?;
assert_eq!(
DomeLight::get(&stage, "/Dome")?
.expect("DomeLight")
.texture_format_attr()
.get()?,
Some(sdf::Value::Token("latlong".into()))
);
let v1 = DomeLight::define_v1(&stage, "/Dome1")?;
v1.create_pole_axis_attr()?.set(sdf::Value::Token("Y".into()))?;
assert_eq!(
DomeLight::get(&stage, "/Dome1")?
.expect("DomeLight_1")
.pole_axis_attr()
.get()?,
Some(sdf::Value::Token("Y".into()))
);
Ok(())
}
#[test]
fn light_filter_is_typed_xformable() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
LightFilter::define(&stage, "/Filter")?;
let f = LightFilter::get(&stage, "/Filter")?.expect("LightFilter");
assert!(f.xform_op_order()?.is_none());
assert!(LightFilter::get(&stage, "/Missing")?.is_none());
Ok(())
}
#[test]
fn light_api_apply_and_get() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
stage.define_prim("/Emitter")?.set_type_name("Mesh")?;
let light = LightAPI::apply(&stage, "/Emitter")?;
light.create_intensity_attr()?.set(1500.0_f32)?;
assert!(stage
.prim_at(sdf::path("/Emitter")?)
.api_schemas()?
.iter()
.any(|s| s == "LightAPI"));
let light = LightAPI::get(&stage, "/Emitter")?.expect("LightAPI");
assert_eq!(light.intensity_attr().get()?, Some(sdf::Value::Float(1500.0)));
assert!(light.is_applied_api_schema());
Ok(())
}
#[test]
fn shaping_and_shadow_roundtrip() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
stage.define_prim("/Spot")?.set_type_name("RectLight")?;
ShapingAPI::apply(&stage, "/Spot")?
.create_cone_angle_attr()?
.set(45.0_f32)?;
ShadowAPI::apply(&stage, "/Spot")?
.create_distance_attr()?
.set(10.0_f32)?;
assert_eq!(
ShapingAPI::get(&stage, "/Spot")?
.expect("ShapingAPI")
.cone_angle_attr()
.get()?,
Some(sdf::Value::Float(45.0))
);
assert_eq!(
ShadowAPI::get(&stage, "/Spot")?
.expect("ShadowAPI")
.distance_attr()
.get()?,
Some(sdf::Value::Float(10.0))
);
Ok(())
}
#[test]
fn light_list_roundtrip() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
stage.define_prim("/World")?.set_type_name("Xform")?;
let list = LightListAPI::apply(&stage, "/World")?;
list.create_light_list_rel()?.set_targets([sdf::path("/World/Sun")?])?;
list.create_cache_behavior_attr()?
.set(sdf::Value::Token("consumeAndContinue".into()))?;
let list = LightListAPI::get(&stage, "/World")?.expect("LightListAPI");
assert_eq!(list.light_list_rel().targets()?, vec![sdf::path("/World/Sun")?]);
assert_eq!(
list.cache_behavior_attr().get()?,
Some(sdf::Value::Token("consumeAndContinue".into()))
);
Ok(())
}
}