Skip to main content

ifc_style/light/
positional.rs

1//! `IfcLightSourcePositional` and its `IfcLightSourceSpot` subtype.
2
3use ifc_model::EntityId;
4
5use crate::error::StyleResult;
6use crate::light::source::LightSource;
7use crate::view::Record;
8
9/// Borrowed projection of `IfcLightSourcePositional`: a point light with a
10/// quadratic distance-attenuation law.
11///
12/// The three attenuation coefficients are plain `IfcReal` in every bundled
13/// schema -- unbounded and unsigned -- so they are returned verbatim. IFC
14/// inherits the law from ISO/IEC 14772-1 (VRML): attenuation at distance `d`
15/// is `1 / max(c + l*d + q*d*d, 1)`. This crate does not evaluate it; clamping
16/// and unit policy belong to whatever renders the light.
17#[derive(Debug, Clone, Copy)]
18pub struct LightSourcePositional<'m, 's> {
19    record: Record<'m, 's>,
20}
21
22impl<'m, 's> LightSourcePositional<'m, 's> {
23    pub(crate) fn from_record(record: Record<'m, 's>) -> Self {
24        Self { record }
25    }
26
27    /// The inherited `IfcLightSource` colour and intensity attributes.
28    pub fn light_source(&self) -> LightSource<'m, 's> {
29        LightSource::from_record(self.record)
30    }
31
32    /// The `Position` attribute: a mandatory `IfcCartesianPoint` reference.
33    ///
34    /// Note the asymmetry with `IfcLightSourceGoniometric`, which uses a full
35    /// `IfcAxis2Placement3D`: a positional light is rotationally symmetric, so
36    /// the schema gives it a bare point and no axes.
37    pub fn position(&self) -> StyleResult<EntityId> {
38        self.record.required_ref("Position", "IfcCartesianPoint")
39    }
40
41    /// The `Radius` attribute, an `IfcPositiveLengthMeasure`.
42    ///
43    /// Reported in the file's own length unit. This crate does not resolve
44    /// `IfcUnitAssignment`; scaling is the caller's job, as it is for every
45    /// other length this crate touches.
46    pub fn radius(&self) -> StyleResult<f64> {
47        self.record.positive("Radius", "IfcPositiveLengthMeasure")
48    }
49
50    /// The `ConstantAttenuation` coefficient.
51    pub fn constant_attenuation(&self) -> StyleResult<f64> {
52        self.record.required_number("ConstantAttenuation")
53    }
54
55    /// The `DistanceAttenuation` (linear) coefficient.
56    pub fn distance_attenuation(&self) -> StyleResult<f64> {
57        self.record.required_number("DistanceAttenuation")
58    }
59
60    /// The `QuadricAttenuation` (quadratic) coefficient.
61    pub fn quadric_attenuation(&self) -> StyleResult<f64> {
62        self.record.required_number("QuadricAttenuation")
63    }
64}
65
66/// Borrowed projection of `IfcLightSourceSpot`: a positional light narrowed to
67/// a cone and aimed along an orientation.
68#[derive(Debug, Clone, Copy)]
69pub struct LightSourceSpot<'m, 's> {
70    record: Record<'m, 's>,
71}
72
73impl<'m, 's> LightSourceSpot<'m, 's> {
74    pub(crate) fn from_record(record: Record<'m, 's>) -> Self {
75        Self { record }
76    }
77
78    /// The inherited `IfcLightSourcePositional` position and attenuation.
79    pub fn positional(&self) -> LightSourcePositional<'m, 's> {
80        LightSourcePositional::from_record(self.record)
81    }
82
83    /// The inherited `IfcLightSource` colour and intensity attributes.
84    pub fn light_source(&self) -> LightSource<'m, 's> {
85        LightSource::from_record(self.record)
86    }
87
88    /// The `Orientation` attribute: a mandatory `IfcDirection` reference
89    /// giving the cone's axis.
90    pub fn orientation(&self) -> StyleResult<EntityId> {
91        self.record.required_ref("Orientation", "IfcDirection")
92    }
93
94    /// The `ConcentrationExponent` attribute, when authored.
95    ///
96    /// OPTIONAL in all three bundled schemas. It is *not* defaulted to `1.0`
97    /// here; an absent exponent means the file expressed no falloff
98    /// preference, which is different from authoring linear falloff.
99    pub fn concentration_exponent(&self) -> StyleResult<Option<f64>> {
100        self.record.optional_number("ConcentrationExponent")
101    }
102
103    /// The `SpreadAngle` attribute, an `IfcPositivePlaneAngleMeasure`.
104    ///
105    /// The half-angle of the full cone, in the file's plane-angle unit --
106    /// commonly radians, but IFC permits degrees via `IfcUnitAssignment`, so
107    /// a caller must resolve units before comparing this to `beam_width_angle`
108    /// from a different file.
109    pub fn spread_angle(&self) -> StyleResult<f64> {
110        self.record
111            .positive("SpreadAngle", "IfcPositivePlaneAngleMeasure")
112    }
113
114    /// The `BeamWidthAngle` attribute, an `IfcPositivePlaneAngleMeasure`:
115    /// the half-angle of the inner, full-intensity cone.
116    pub fn beam_width_angle(&self) -> StyleResult<f64> {
117        self.record
118            .positive("BeamWidthAngle", "IfcPositivePlaneAngleMeasure")
119    }
120}