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}