use anyhow::Result;
use crate::sdf::{Path, Value};
use crate::usd::Stage;
use super::tokens::*;
use super::types::*;
pub fn read_light_api(stage: &Stage, prim: &Path) -> Result<Option<ReadLight>> {
read_light_api_inner(stage, prim, None)
}
pub fn read_light_api_at(stage: &Stage, prim: &Path, time: f64) -> Result<Option<ReadLight>> {
read_light_api_inner(stage, prim, Some(time))
}
fn read_light_api_inner(stage: &Stage, prim: &Path, time: Option<f64>) -> Result<Option<ReadLight>> {
let type_name = stage.type_name(prim)?;
if type_name.as_deref() == Some(T_DISTANT_LIGHT) {
let intensity_fallback = ReadDistantLight::default().common.intensity;
return Ok(Some(read_light_inputs_with_intensity(
stage,
prim,
time,
intensity_fallback,
)?));
}
if type_name.as_deref().is_some_and(is_light_type) || has_light_api_schema(stage, prim)? {
return Ok(Some(read_light_inputs(stage, prim, time)?));
}
Ok(None)
}
fn has_light_api_schema(stage: &Stage, prim: &Path) -> Result<bool> {
Ok(stage.api_schemas(prim)?.iter().any(|s| is_light_api_schema(s)))
}
fn is_light_api_schema(name: &str) -> bool {
matches!(name, API_LIGHT | API_MESH_LIGHT | API_VOLUME_LIGHT)
}
fn read_light_inputs(stage: &Stage, prim: &Path, time: Option<f64>) -> Result<ReadLight> {
read_light_inputs_with_intensity(stage, prim, time, ReadLight::default().intensity)
}
fn read_light_inputs_with_intensity(
stage: &Stage,
prim: &Path,
time: Option<f64>,
intensity_fallback: f32,
) -> Result<ReadLight> {
let defaults = ReadLight::default();
Ok(ReadLight {
path: prim.as_str().to_string(),
intensity: read_f32(stage, prim, A_INTENSITY, time)?.unwrap_or(intensity_fallback),
exposure: read_f32(stage, prim, A_EXPOSURE, time)?.unwrap_or(defaults.exposure),
diffuse: read_f32(stage, prim, A_DIFFUSE, time)?.unwrap_or(defaults.diffuse),
specular: read_f32(stage, prim, A_SPECULAR, time)?.unwrap_or(defaults.specular),
normalize: read_bool(stage, prim, A_NORMALIZE, time)?.unwrap_or(defaults.normalize),
color: read_color3f(stage, prim, A_COLOR, time)?.unwrap_or(defaults.color),
enable_color_temperature: read_bool(stage, prim, A_ENABLE_COLOR_TEMPERATURE, time)?
.unwrap_or(defaults.enable_color_temperature),
color_temperature: read_f32(stage, prim, A_COLOR_TEMPERATURE, time)?.unwrap_or(defaults.color_temperature),
filters: read_rel_targets(stage, prim, REL_FILTERS)?,
})
}
pub fn read_distant_light(stage: &Stage, prim: &Path) -> Result<Option<ReadDistantLight>> {
read_distant_light_inner(stage, prim, None)
}
pub fn read_distant_light_at(stage: &Stage, prim: &Path, time: f64) -> Result<Option<ReadDistantLight>> {
read_distant_light_inner(stage, prim, Some(time))
}
fn read_distant_light_inner(stage: &Stage, prim: &Path, time: Option<f64>) -> Result<Option<ReadDistantLight>> {
if stage.type_name(prim)?.as_deref() != Some(T_DISTANT_LIGHT) {
return Ok(None);
}
let defaults = ReadDistantLight::default();
Ok(Some(ReadDistantLight {
common: read_light_inputs_with_intensity(stage, prim, time, defaults.common.intensity)?,
angle_deg: read_f32(stage, prim, A_ANGLE, time)?.unwrap_or(defaults.angle_deg),
}))
}
pub fn read_sphere_light(stage: &Stage, prim: &Path) -> Result<Option<ReadSphereLight>> {
read_sphere_light_inner(stage, prim, None)
}
pub fn read_sphere_light_at(stage: &Stage, prim: &Path, time: f64) -> Result<Option<ReadSphereLight>> {
read_sphere_light_inner(stage, prim, Some(time))
}
fn read_sphere_light_inner(stage: &Stage, prim: &Path, time: Option<f64>) -> Result<Option<ReadSphereLight>> {
if stage.type_name(prim)?.as_deref() != Some(T_SPHERE_LIGHT) {
return Ok(None);
}
let defaults = ReadSphereLight::default();
Ok(Some(ReadSphereLight {
common: read_light_inputs(stage, prim, time)?,
radius: read_f32(stage, prim, A_RADIUS, time)?.unwrap_or(defaults.radius),
treat_as_point: read_bool(stage, prim, A_TREAT_AS_POINT, time)?.unwrap_or(defaults.treat_as_point),
}))
}
pub fn read_rect_light(stage: &Stage, prim: &Path) -> Result<Option<ReadRectLight>> {
read_rect_light_inner(stage, prim, None)
}
pub fn read_rect_light_at(stage: &Stage, prim: &Path, time: f64) -> Result<Option<ReadRectLight>> {
read_rect_light_inner(stage, prim, Some(time))
}
fn read_rect_light_inner(stage: &Stage, prim: &Path, time: Option<f64>) -> Result<Option<ReadRectLight>> {
if stage.type_name(prim)?.as_deref() != Some(T_RECT_LIGHT) {
return Ok(None);
}
let defaults = ReadRectLight::default();
Ok(Some(ReadRectLight {
common: read_light_inputs(stage, prim, time)?,
width: read_f32(stage, prim, A_WIDTH, time)?.unwrap_or(defaults.width),
height: read_f32(stage, prim, A_HEIGHT, time)?.unwrap_or(defaults.height),
texture_file: read_asset_path(stage, prim, A_TEXTURE_FILE, time)?,
}))
}
pub fn read_disk_light(stage: &Stage, prim: &Path) -> Result<Option<ReadDiskLight>> {
read_disk_light_inner(stage, prim, None)
}
pub fn read_disk_light_at(stage: &Stage, prim: &Path, time: f64) -> Result<Option<ReadDiskLight>> {
read_disk_light_inner(stage, prim, Some(time))
}
fn read_disk_light_inner(stage: &Stage, prim: &Path, time: Option<f64>) -> Result<Option<ReadDiskLight>> {
if stage.type_name(prim)?.as_deref() != Some(T_DISK_LIGHT) {
return Ok(None);
}
let defaults = ReadDiskLight::default();
Ok(Some(ReadDiskLight {
common: read_light_inputs(stage, prim, time)?,
radius: read_f32(stage, prim, A_RADIUS, time)?.unwrap_or(defaults.radius),
}))
}
pub fn read_cylinder_light(stage: &Stage, prim: &Path) -> Result<Option<ReadCylinderLight>> {
read_cylinder_light_inner(stage, prim, None)
}
pub fn read_cylinder_light_at(stage: &Stage, prim: &Path, time: f64) -> Result<Option<ReadCylinderLight>> {
read_cylinder_light_inner(stage, prim, Some(time))
}
fn read_cylinder_light_inner(stage: &Stage, prim: &Path, time: Option<f64>) -> Result<Option<ReadCylinderLight>> {
if stage.type_name(prim)?.as_deref() != Some(T_CYLINDER_LIGHT) {
return Ok(None);
}
let defaults = ReadCylinderLight::default();
Ok(Some(ReadCylinderLight {
common: read_light_inputs(stage, prim, time)?,
length: read_f32(stage, prim, A_LENGTH, time)?.unwrap_or(defaults.length),
radius: read_f32(stage, prim, A_RADIUS, time)?.unwrap_or(defaults.radius),
treat_as_line: read_bool(stage, prim, A_TREAT_AS_LINE, time)?.unwrap_or(defaults.treat_as_line),
}))
}
pub fn read_dome_light(stage: &Stage, prim: &Path) -> Result<Option<ReadDomeLight>> {
read_dome_light_inner(stage, prim, None)
}
pub fn read_dome_light_at(stage: &Stage, prim: &Path, time: f64) -> Result<Option<ReadDomeLight>> {
read_dome_light_inner(stage, prim, Some(time))
}
fn read_dome_light_inner(stage: &Stage, prim: &Path, time: Option<f64>) -> Result<Option<ReadDomeLight>> {
let type_name = stage.type_name(prim)?;
if !matches!(type_name.as_deref(), Some(T_DOME_LIGHT) | Some(T_DOME_LIGHT_1)) {
return Ok(None);
}
let is_v1_1 = type_name.as_deref() == Some(T_DOME_LIGHT_1);
let defaults = ReadDomeLight::default();
Ok(Some(ReadDomeLight {
common: read_light_inputs(stage, prim, time)?,
texture_file: read_asset_path(stage, prim, A_TEXTURE_FILE, time)?,
texture_format: read_token(stage, prim, A_TEXTURE_FORMAT, time)?
.as_deref()
.and_then(TextureFormat::from_token)
.unwrap_or_default(),
portals: read_rel_targets(stage, prim, REL_PORTALS)?,
guide_radius: read_f32(stage, prim, A_GUIDE_RADIUS, time)?.unwrap_or(defaults.guide_radius),
pole_axis: if is_v1_1 {
read_token(stage, prim, A_POLE_AXIS, time)?
.as_deref()
.and_then(PoleAxis::from_token)
} else {
None
},
}))
}
pub fn read_geometry_light(stage: &Stage, prim: &Path) -> Result<Option<ReadGeometryLight>> {
read_geometry_light_inner(stage, prim, None)
}
pub fn read_geometry_light_at(stage: &Stage, prim: &Path, time: f64) -> Result<Option<ReadGeometryLight>> {
read_geometry_light_inner(stage, prim, Some(time))
}
fn read_geometry_light_inner(stage: &Stage, prim: &Path, time: Option<f64>) -> Result<Option<ReadGeometryLight>> {
if stage.type_name(prim)?.as_deref() != Some(T_GEOMETRY_LIGHT) {
return Ok(None);
}
Ok(Some(ReadGeometryLight {
common: read_light_inputs(stage, prim, time)?,
geometry: read_rel_first_target(stage, prim, REL_GEOMETRY)?,
}))
}
pub fn read_portal_light(stage: &Stage, prim: &Path) -> Result<Option<ReadPortalLight>> {
read_portal_light_inner(stage, prim, None)
}
pub fn read_portal_light_at(stage: &Stage, prim: &Path, time: f64) -> Result<Option<ReadPortalLight>> {
read_portal_light_inner(stage, prim, Some(time))
}
fn read_portal_light_inner(stage: &Stage, prim: &Path, time: Option<f64>) -> Result<Option<ReadPortalLight>> {
if stage.type_name(prim)?.as_deref() != Some(T_PORTAL_LIGHT) {
return Ok(None);
}
let defaults = ReadPortalLight::default();
Ok(Some(ReadPortalLight {
common: read_light_inputs(stage, prim, time)?,
width: read_f32(stage, prim, A_WIDTH, time)?.unwrap_or(defaults.width),
height: read_f32(stage, prim, A_HEIGHT, time)?.unwrap_or(defaults.height),
}))
}
pub fn read_light(stage: &Stage, prim: &Path) -> Result<Option<ReadAnyLight>> {
read_light_dispatch(stage, prim, None)
}
pub fn read_light_at(stage: &Stage, prim: &Path, time: f64) -> Result<Option<ReadAnyLight>> {
read_light_dispatch(stage, prim, Some(time))
}
fn read_light_dispatch(stage: &Stage, prim: &Path, time: Option<f64>) -> Result<Option<ReadAnyLight>> {
let Some(type_name) = stage.type_name(prim)? else {
return Ok(None);
};
Ok(match type_name.as_str() {
T_DISTANT_LIGHT => read_distant_light_inner(stage, prim, time)?.map(ReadAnyLight::Distant),
T_SPHERE_LIGHT => read_sphere_light_inner(stage, prim, time)?.map(ReadAnyLight::Sphere),
T_RECT_LIGHT => read_rect_light_inner(stage, prim, time)?.map(ReadAnyLight::Rect),
T_DISK_LIGHT => read_disk_light_inner(stage, prim, time)?.map(ReadAnyLight::Disk),
T_CYLINDER_LIGHT => read_cylinder_light_inner(stage, prim, time)?.map(ReadAnyLight::Cylinder),
T_DOME_LIGHT | T_DOME_LIGHT_1 => read_dome_light_inner(stage, prim, time)?.map(ReadAnyLight::Dome),
T_GEOMETRY_LIGHT => read_geometry_light_inner(stage, prim, time)?.map(ReadAnyLight::Geometry),
T_PORTAL_LIGHT => read_portal_light_inner(stage, prim, time)?.map(ReadAnyLight::Portal),
_ => None,
})
}
pub fn is_light_type(type_name: &str) -> bool {
matches!(
type_name,
T_DISTANT_LIGHT
| T_SPHERE_LIGHT
| T_RECT_LIGHT
| T_DISK_LIGHT
| T_CYLINDER_LIGHT
| T_DOME_LIGHT
| T_DOME_LIGHT_1
| T_GEOMETRY_LIGHT
| T_PORTAL_LIGHT
)
}
pub fn read_shaping(stage: &Stage, prim: &Path) -> Result<Option<ReadShaping>> {
read_shaping_inner(stage, prim, None)
}
pub fn read_shaping_at(stage: &Stage, prim: &Path, time: f64) -> Result<Option<ReadShaping>> {
read_shaping_inner(stage, prim, Some(time))
}
fn read_shaping_inner(stage: &Stage, prim: &Path, time: Option<f64>) -> Result<Option<ReadShaping>> {
if !stage.has_api_schema(prim, API_SHAPING)? {
return Ok(None);
}
let defaults = ReadShaping::default();
Ok(Some(ReadShaping {
focus: read_f32(stage, prim, A_SHAPING_FOCUS, time)?.unwrap_or(defaults.focus),
focus_tint: read_color3f(stage, prim, A_SHAPING_FOCUS_TINT, time)?.unwrap_or(defaults.focus_tint),
cone_angle_deg: read_f32(stage, prim, A_SHAPING_CONE_ANGLE, time)?.unwrap_or(defaults.cone_angle_deg),
cone_softness: read_f32(stage, prim, A_SHAPING_CONE_SOFTNESS, time)?.unwrap_or(defaults.cone_softness),
ies_file: read_asset_path(stage, prim, A_SHAPING_IES_FILE, time)?,
ies_angle_scale: read_f32(stage, prim, A_SHAPING_IES_ANGLE_SCALE, time)?.unwrap_or(defaults.ies_angle_scale),
ies_normalize: read_bool(stage, prim, A_SHAPING_IES_NORMALIZE, time)?.unwrap_or(defaults.ies_normalize),
}))
}
pub fn read_shadow(stage: &Stage, prim: &Path) -> Result<Option<ReadShadow>> {
read_shadow_inner(stage, prim, None)
}
pub fn read_shadow_at(stage: &Stage, prim: &Path, time: f64) -> Result<Option<ReadShadow>> {
read_shadow_inner(stage, prim, Some(time))
}
fn read_shadow_inner(stage: &Stage, prim: &Path, time: Option<f64>) -> Result<Option<ReadShadow>> {
if !stage.has_api_schema(prim, API_SHADOW)? {
return Ok(None);
}
let defaults = ReadShadow::default();
Ok(Some(ReadShadow {
enable: read_bool(stage, prim, A_SHADOW_ENABLE, time)?.unwrap_or(defaults.enable),
color: read_color3f(stage, prim, A_SHADOW_COLOR, time)?.unwrap_or(defaults.color),
distance: read_f32(stage, prim, A_SHADOW_DISTANCE, time)?.unwrap_or(defaults.distance),
falloff: read_f32(stage, prim, A_SHADOW_FALLOFF, time)?.unwrap_or(defaults.falloff),
falloff_gamma: read_f32(stage, prim, A_SHADOW_FALLOFF_GAMMA, time)?.unwrap_or(defaults.falloff_gamma),
}))
}
pub fn read_light_list(stage: &Stage, prim: &Path) -> Result<Option<ReadLightList>> {
if !stage.has_api_schema(prim, API_LIGHT_LIST)? {
return Ok(None);
}
Ok(Some(ReadLightList {
lights: read_rel_targets(stage, prim, REL_LIGHT_LIST)?,
cache_behavior: read_token(stage, prim, A_LIGHT_LIST_CACHE_BEHAVIOR, None)?
.as_deref()
.and_then(LightListCacheBehavior::from_token)
.unwrap_or_default(),
}))
}
pub fn find_lux_prims(stage: &Stage) -> Result<LuxPrims> {
let mut out = LuxPrims::default();
let mut err: Result<()> = Ok(());
stage.traverse(|path| {
if err.is_err() {
return;
}
err = bucket_lux_prim(stage, path, &mut out);
})?;
err?;
Ok(out)
}
fn bucket_lux_prim(stage: &Stage, path: &Path, out: &mut LuxPrims) -> Result<()> {
let p = path.as_str().to_string();
let mut concrete_typed = true;
if let Some(type_name) = stage.type_name(path)? {
match type_name.as_str() {
T_DISTANT_LIGHT => out.distant.push(p.clone()),
T_SPHERE_LIGHT => out.sphere.push(p.clone()),
T_RECT_LIGHT => out.rect.push(p.clone()),
T_DISK_LIGHT => out.disk.push(p.clone()),
T_CYLINDER_LIGHT => out.cylinder.push(p.clone()),
T_DOME_LIGHT | T_DOME_LIGHT_1 => out.dome.push(p.clone()),
T_GEOMETRY_LIGHT => out.geometry.push(p.clone()),
T_PORTAL_LIGHT => out.portal.push(p.clone()),
T_LIGHT_FILTER => {
out.light_filters.push(p.clone());
concrete_typed = false;
}
_ => concrete_typed = false,
}
} else {
concrete_typed = false;
}
let api = stage.api_schemas(path)?;
if api.iter().any(|s| s == API_SHAPING) {
out.shaping.push(p.clone());
}
if api.iter().any(|s| s == API_SHADOW) {
out.shadow.push(p.clone());
}
if api.iter().any(|s| s == API_LIGHT_LIST) {
out.light_list.push(p.clone());
}
if !concrete_typed && api.iter().any(|s| is_light_api_schema(s)) {
out.light_api.push(p);
}
Ok(())
}
fn attr_value(stage: &Stage, prim: &Path, name: &str, time: Option<f64>) -> Result<Option<Value>> {
let attr = prim.append_property(name)?;
match time {
None => stage.field::<Value>(attr, "default"),
Some(t) => stage.value_at(attr, t),
}
}
fn read_f32(stage: &Stage, prim: &Path, name: &str, time: Option<f64>) -> Result<Option<f32>> {
Ok(match attr_value(stage, prim, name, time)? {
Some(Value::Float(f)) => Some(f),
Some(Value::Double(d)) => Some(d.clamp(f32::MIN as f64, f32::MAX as f64) as f32),
Some(Value::Half(h)) => Some(h.to_f32()),
_ => None,
})
}
fn read_bool(stage: &Stage, prim: &Path, name: &str, time: Option<f64>) -> Result<Option<bool>> {
Ok(match attr_value(stage, prim, name, time)? {
Some(Value::Bool(b)) => Some(b),
_ => None,
})
}
fn read_color3f(stage: &Stage, prim: &Path, name: &str, time: Option<f64>) -> Result<Option<[f32; 3]>> {
Ok(match attr_value(stage, prim, name, time)? {
Some(Value::Vec3f(v)) => Some(v),
Some(Value::Vec3d(v)) => Some([v[0] as f32, v[1] as f32, v[2] as f32]),
Some(Value::Vec3h(v)) => Some([v[0].to_f32(), v[1].to_f32(), v[2].to_f32()]),
_ => None,
})
}
fn read_token(stage: &Stage, prim: &Path, name: &str, time: Option<f64>) -> Result<Option<String>> {
Ok(match attr_value(stage, prim, name, time)? {
Some(Value::Token(s) | Value::String(s)) => Some(s),
_ => None,
})
}
fn read_asset_path(stage: &Stage, prim: &Path, name: &str, time: Option<f64>) -> Result<Option<String>> {
Ok(match attr_value(stage, prim, name, time)? {
Some(Value::AssetPath(s) | Value::String(s) | Value::Token(s)) => Some(s),
_ => None,
})
}
fn read_rel_first_target(stage: &Stage, prim: &Path, rel_name: &str) -> Result<Option<String>> {
Ok(read_rel_targets(stage, prim, rel_name)?.into_iter().next())
}
fn read_rel_targets(stage: &Stage, prim: &Path, rel_name: &str) -> Result<Vec<String>> {
let rel_path = prim.append_property(rel_name)?;
let raw = stage.field::<Value>(rel_path, "targetPaths")?;
let paths = match raw {
Some(Value::PathListOp(op)) => op.flatten(),
Some(Value::PathVec(v)) => v,
_ => Vec::new(),
};
Ok(paths.into_iter().map(|p| p.as_str().to_string()).collect())
}