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