Skip to main content

dis_rs/common/entity_state/
model.rs

1use crate::common::model::{
2    EntityId, EntityType, Location, Orientation, PduBody, VariableParameter, VectorF32,
3};
4use crate::common::{BodyInfo, Interaction};
5use crate::constants::{FOUR_OCTETS, TWELVE_OCTETS, VARIABLE_PARAMETER_RECORD_LENGTH};
6use crate::entity_state::builder::EntityStateBuilder;
7use crate::enumerations::{
8    AirPlatformAppearance, AppearanceEntityOrObjectState, CulturalFeatureAppearance,
9    DeadReckoningAlgorithm, EntityCapabilities, EntityKind, EntityMarkingCharacterSet,
10    EnvironmentalAppearance, ExpendableAppearance, ForceId, LandPlatformAppearance,
11    LifeFormsAppearance, MunitionAppearance, PduType, PlatformDomain, RadioAppearance,
12    SensorEmitterAppearance, SpacePlatformAppearance, SubsurfacePlatformAppearance,
13    SupplyAppearance, SurfacePlatformAppearance,
14};
15use crate::{BodyRaw, DisError};
16use alloc::{
17    format,
18    string::{String, ToString},
19    vec::Vec,
20};
21use core::str::FromStr;
22#[cfg(feature = "serde")]
23use serde::{Deserialize, Serialize};
24
25const BASE_ENTITY_STATE_BODY_LENGTH: u16 = 132;
26
27// TODO sensible errors for EntityState
28#[allow(dead_code)]
29pub enum EntityStateValidationError {
30    SomeFieldNotOkError,
31}
32
33/// 5.3.2 Entity State PDU
34///
35/// 7.2.2 Entity State PDU
36#[derive(Clone, Debug, Default, PartialEq)]
37#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
38pub struct EntityState {
39    pub entity_id: EntityId,                     // struct
40    pub force_id: ForceId,                       // enum
41    pub entity_type: EntityType,                 // struct
42    pub alternative_entity_type: EntityType,     // struct
43    pub entity_linear_velocity: VectorF32,       // struct
44    pub entity_location: Location,               // struct
45    pub entity_orientation: Orientation,         // struct
46    pub entity_appearance: EntityAppearance,     // enum
47    pub dead_reckoning_parameters: DrParameters, // struct
48    pub entity_marking: EntityMarking,           // struct
49    pub entity_capabilities: EntityCapabilities, // enum
50    pub variable_parameters: Vec<VariableParameter>,
51}
52
53impl BodyRaw for EntityState {
54    type Builder = EntityStateBuilder;
55
56    fn builder() -> Self::Builder {
57        Self::Builder::new()
58    }
59
60    fn into_builder(self) -> Self::Builder {
61        Self::Builder::new_from_body(self)
62    }
63
64    fn into_pdu_body(self) -> PduBody {
65        PduBody::EntityState(self)
66    }
67}
68
69impl BodyInfo for EntityState {
70    fn body_length(&self) -> u16 {
71        BASE_ENTITY_STATE_BODY_LENGTH
72            + (VARIABLE_PARAMETER_RECORD_LENGTH * (self.variable_parameters.len() as u16))
73    }
74
75    fn body_type(&self) -> PduType {
76        PduType::EntityState
77    }
78}
79
80impl Interaction for EntityState {
81    fn originator(&self) -> Option<&EntityId> {
82        Some(&self.entity_id)
83    }
84
85    fn receiver(&self) -> Option<&EntityId> {
86        None
87    }
88}
89
90/// 6.2.26 Entity Appearance record
91#[derive(Copy, Clone, Debug, PartialEq)]
92#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
93pub enum EntityAppearance {
94    LandPlatform(LandPlatformAppearance),
95    AirPlatform(AirPlatformAppearance),
96    SurfacePlatform(SurfacePlatformAppearance),
97    SubsurfacePlatform(SubsurfacePlatformAppearance),
98    SpacePlatform(SpacePlatformAppearance),
99    Munition(MunitionAppearance),
100    LifeForms(LifeFormsAppearance),
101    Environmental(EnvironmentalAppearance),
102    CulturalFeature(CulturalFeatureAppearance),
103    Supply(SupplyAppearance),
104    Radio(RadioAppearance),
105    Expendable(ExpendableAppearance),
106    SensorEmitter(SensorEmitterAppearance),
107    Unspecified([u8; FOUR_OCTETS]),
108}
109
110impl Default for EntityAppearance {
111    fn default() -> Self {
112        Self::Unspecified(0u32.to_be_bytes())
113    }
114}
115
116impl EntityAppearance {
117    #[must_use]
118    #[allow(clippy::match_same_arms)]
119    pub fn from_bytes(appearance: u32, entity_type: &EntityType) -> Self {
120        match (entity_type.kind, entity_type.domain) {
121            (EntityKind::Other, _) => EntityAppearance::Unspecified(appearance.to_be_bytes()),
122            (EntityKind::Platform, PlatformDomain::Land) => {
123                EntityAppearance::LandPlatform(LandPlatformAppearance::from(appearance))
124            }
125            (EntityKind::Platform, PlatformDomain::Air) => {
126                EntityAppearance::AirPlatform(AirPlatformAppearance::from(appearance))
127            }
128            (EntityKind::Platform, PlatformDomain::Surface) => {
129                EntityAppearance::SurfacePlatform(SurfacePlatformAppearance::from(appearance))
130            }
131            (EntityKind::Platform, PlatformDomain::Subsurface) => {
132                EntityAppearance::SubsurfacePlatform(SubsurfacePlatformAppearance::from(appearance))
133            }
134            (EntityKind::Platform, PlatformDomain::Space) => {
135                EntityAppearance::SpacePlatform(SpacePlatformAppearance::from(appearance))
136            }
137            (EntityKind::Munition, _) => {
138                EntityAppearance::Munition(MunitionAppearance::from(appearance))
139            }
140            (EntityKind::LifeForm, _) => {
141                EntityAppearance::LifeForms(LifeFormsAppearance::from(appearance))
142            }
143            (EntityKind::Environmental, _) => {
144                EntityAppearance::Environmental(EnvironmentalAppearance::from(appearance))
145            }
146            (EntityKind::CulturalFeature, _) => {
147                EntityAppearance::CulturalFeature(CulturalFeatureAppearance::from(appearance))
148            }
149            (EntityKind::Supply, _) => EntityAppearance::Supply(SupplyAppearance::from(appearance)),
150            (EntityKind::Radio, _) => EntityAppearance::Radio(RadioAppearance::from(appearance)),
151            (EntityKind::Expendable, _) => {
152                EntityAppearance::Expendable(ExpendableAppearance::from(appearance))
153            }
154            (EntityKind::SensorEmitter, _) => {
155                EntityAppearance::SensorEmitter(SensorEmitterAppearance::from(appearance))
156            }
157            (_, _) => EntityAppearance::Unspecified(appearance.to_be_bytes()),
158        }
159    }
160
161    #[must_use]
162    pub const fn record_length(&self) -> u16 {
163        FOUR_OCTETS as u16
164    }
165
166    #[must_use]
167    pub fn state(&self) -> Option<AppearanceEntityOrObjectState> {
168        match self {
169            EntityAppearance::LandPlatform(appearance) => Some(appearance.state),
170            EntityAppearance::AirPlatform(appearance) => Some(appearance.state),
171            EntityAppearance::SurfacePlatform(appearance) => Some(appearance.state),
172            EntityAppearance::SubsurfacePlatform(appearance) => Some(appearance.state),
173            EntityAppearance::SpacePlatform(appearance) => Some(appearance.state),
174            EntityAppearance::Munition(appearance) => Some(appearance.state),
175            EntityAppearance::LifeForms(appearance) => Some(appearance.state),
176            EntityAppearance::Environmental(appearance) => Some(appearance.state),
177            EntityAppearance::CulturalFeature(appearance) => Some(appearance.state),
178            EntityAppearance::Supply(appearance) => Some(appearance.state),
179            EntityAppearance::Radio(appearance) => Some(appearance.state),
180            EntityAppearance::Expendable(appearance) => Some(appearance.state),
181            EntityAppearance::SensorEmitter(appearance) => Some(appearance.state),
182            EntityAppearance::Unspecified(_) => None,
183        }
184    }
185
186    #[must_use]
187    pub fn is_frozen(&self) -> Option<bool> {
188        match self {
189            EntityAppearance::LandPlatform(appearance) => Some(appearance.is_frozen),
190            EntityAppearance::AirPlatform(appearance) => Some(appearance.is_frozen),
191            EntityAppearance::SurfacePlatform(appearance) => Some(appearance.is_frozen),
192            EntityAppearance::SubsurfacePlatform(appearance) => Some(appearance.is_frozen),
193            EntityAppearance::SpacePlatform(appearance) => Some(appearance.is_frozen),
194            EntityAppearance::Munition(appearance) => Some(appearance.is_frozen),
195            EntityAppearance::LifeForms(appearance) => Some(appearance.is_frozen),
196            EntityAppearance::Environmental(appearance) => Some(appearance.is_frozen),
197            EntityAppearance::CulturalFeature(appearance) => Some(appearance.is_frozen),
198            EntityAppearance::Supply(appearance) => Some(appearance.is_frozen),
199            EntityAppearance::Radio(appearance) => Some(appearance.is_frozen),
200            EntityAppearance::Expendable(appearance) => Some(appearance.is_frozen),
201            EntityAppearance::SensorEmitter(appearance) => Some(appearance.is_frozen),
202            EntityAppearance::Unspecified(_) => None,
203        }
204    }
205}
206
207impl From<&EntityAppearance> for u32 {
208    #[allow(clippy::match_same_arms)]
209    fn from(value: &EntityAppearance) -> Self {
210        match value {
211            EntityAppearance::LandPlatform(appearance) => u32::from(*appearance),
212            EntityAppearance::AirPlatform(appearance) => u32::from(*appearance),
213            EntityAppearance::SurfacePlatform(appearance) => u32::from(*appearance),
214            EntityAppearance::SubsurfacePlatform(appearance) => u32::from(*appearance),
215            EntityAppearance::SpacePlatform(appearance) => u32::from(*appearance),
216            EntityAppearance::Munition(appearance) => u32::from(*appearance),
217            EntityAppearance::LifeForms(appearance) => u32::from(*appearance),
218            EntityAppearance::Environmental(appearance) => u32::from(*appearance),
219            EntityAppearance::CulturalFeature(appearance) => u32::from(*appearance),
220            EntityAppearance::Supply(appearance) => u32::from(*appearance),
221            EntityAppearance::Radio(appearance) => u32::from(*appearance),
222            EntityAppearance::Expendable(appearance) => u32::from(*appearance),
223            EntityAppearance::SensorEmitter(appearance) => u32::from(*appearance),
224            EntityAppearance::Unspecified(appearance) => u32::from_be_bytes(*appearance),
225        }
226    }
227}
228
229/// 6.2.29 Entity Marking record
230#[derive(Clone, Debug, PartialEq)]
231#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
232pub struct EntityMarking {
233    pub marking_character_set: EntityMarkingCharacterSet,
234    pub marking_string: String, // 11 byte String
235}
236
237impl EntityMarking {
238    pub fn new(marking: impl Into<String>, character_set: EntityMarkingCharacterSet) -> Self {
239        Self {
240            marking_character_set: character_set,
241            marking_string: marking.into(),
242        }
243    }
244
245    pub fn new_ascii<S: Into<String>>(marking: S) -> Self {
246        EntityMarking::new(marking.into(), EntityMarkingCharacterSet::ASCII)
247    }
248
249    #[allow(clippy::return_self_not_must_use)]
250    pub fn with_marking<S: Into<String>>(mut self, marking: S) -> Self {
251        self.marking_string = marking.into();
252        self
253    }
254
255    #[must_use]
256    pub fn record_length(&self) -> u16 {
257        TWELVE_OCTETS as u16
258    }
259}
260
261impl Default for EntityMarking {
262    fn default() -> Self {
263        Self {
264            marking_character_set: EntityMarkingCharacterSet::ASCII,
265            marking_string: String::from("Marking"),
266        }
267    }
268}
269
270impl FromStr for EntityMarking {
271    type Err = DisError;
272
273    fn from_str(s: &str) -> Result<Self, Self::Err> {
274        if s.len() <= 11 {
275            Ok(Self {
276                marking_character_set: EntityMarkingCharacterSet::ASCII,
277                marking_string: s.to_string(),
278            })
279        } else {
280            Err(DisError::ParseError(format!(
281                "String is too long for EntityMarking. Found {}, max 11 allowed.",
282                s.len()
283            )))
284        }
285    }
286}
287
288/// Custom defined record to group Dead Reckoning Parameters
289#[derive(Clone, Default, Debug, PartialEq)]
290#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
291pub struct DrParameters {
292    pub algorithm: DeadReckoningAlgorithm,
293    pub other_parameters: DrOtherParameters,
294    pub linear_acceleration: VectorF32,
295    pub angular_velocity: VectorF32,
296}
297
298impl DrParameters {
299    #[must_use]
300    pub fn with_algorithm(mut self, algorithm: DeadReckoningAlgorithm) -> Self {
301        self.algorithm = algorithm;
302        self
303    }
304
305    #[must_use]
306    pub fn with_parameters(mut self, parameters: DrOtherParameters) -> Self {
307        self.other_parameters = parameters;
308        self
309    }
310
311    #[must_use]
312    pub fn with_linear_acceleration(mut self, linear_acceleration: VectorF32) -> Self {
313        self.linear_acceleration = linear_acceleration;
314        self
315    }
316
317    #[must_use]
318    pub fn with_angular_velocity(mut self, angular_velocity: VectorF32) -> Self {
319        self.angular_velocity = angular_velocity;
320        self
321    }
322}
323
324/// E.8 Use of the Other Parameters field in Dead Reckoning Parameters
325#[derive(Clone, Debug, PartialEq)]
326#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
327#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
328pub enum DrOtherParameters {
329    None([u8; 15]),
330    LocalEulerAngles(DrEulerAngles),
331    WorldOrientationQuaternion(DrWorldOrientationQuaternion),
332}
333
334impl Default for DrOtherParameters {
335    fn default() -> Self {
336        Self::None([0u8; 15])
337    }
338}
339
340/// Identical to Table 58—Euler Angles record / 6.2.32 Euler Angles record (which is modeled as `VectorF32`)
341#[derive(Clone, Default, Debug, PartialEq)]
342#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
343pub struct DrEulerAngles {
344    pub local_yaw: f32,
345    pub local_pitch: f32,
346    pub local_roll: f32,
347}
348
349impl DrEulerAngles {
350    #[must_use]
351    pub fn with_local_yaw(mut self, local_yaw: f32) -> Self {
352        self.local_yaw = local_yaw;
353        self
354    }
355
356    #[must_use]
357    pub fn with_local_pitch(mut self, local_pitch: f32) -> Self {
358        self.local_pitch = local_pitch;
359        self
360    }
361
362    #[must_use]
363    pub fn with_local_roll(mut self, local_roll: f32) -> Self {
364        self.local_roll = local_roll;
365        self
366    }
367}
368
369/// Table E.3—World Orientation Quaternion Dead Reckoning Parameters (E.8.2.3 Rotating DRM entities)
370#[derive(Clone, Default, Debug, PartialEq)]
371#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
372pub struct DrWorldOrientationQuaternion {
373    pub nil: u16,
374    pub x: f32,
375    pub y: f32,
376    pub z: f32,
377}
378
379impl DrWorldOrientationQuaternion {
380    #[must_use]
381    pub fn with_nil(mut self, nil: u16) -> Self {
382        self.nil = nil;
383        self
384    }
385
386    #[must_use]
387    pub fn with_x(mut self, x: f32) -> Self {
388        self.x = x;
389        self
390    }
391
392    #[must_use]
393    pub fn with_y(mut self, y: f32) -> Self {
394        self.y = y;
395        self
396    }
397
398    #[must_use]
399    pub fn with_z(mut self, z: f32) -> Self {
400        self.z = z;
401        self
402    }
403}