c3dio/
events.rs

1//! Includes event information from the C3D file header and parameter section.
2use crate::parameters::{Parameter, ParameterData, Parameters};
3use crate::processor::Processor;
4use crate::{C3dWriteError, C3dParseError};
5use grid::Grid;
6use std::collections::HashMap;
7/// # Events
8///
9/// Events are time points in the C3D file that are marked with a label.
10/// The label is a 4-character string that can be used to identify the event.
11/// The label is optional, and if it is not present, the event is still marked
12/// with a time point.
13/// The events are stored in the C3D file header.
14
15/// The `Events` struct contains the events from the C3D file header.
16/// Event information can be included in the parameter section of the C3D file
17/// as well, that information is stored in the `Parameter` struct.
18use std::ops::{Deref, DerefMut};
19
20#[derive(Debug, Clone, PartialEq, Default)]
21pub struct EventContext {
22    pub used: Option<i16>,
23    pub icon_ids: Option<Vec<u16>>,
24    pub labels: Option<Vec<String>>,
25    pub descriptions: Option<Vec<String>>,
26    pub colours: Option<Vec<[u8; 3]>>,
27}
28
29impl EventContext {
30    pub fn new() -> Self {
31        EventContext::default()
32    }
33
34    pub(crate) fn from_parameters(parameters: &mut Parameters) -> Result<Self, C3dParseError> {
35        let used = parameters.remove("EVENT_CONTEXT", "USED");
36        let used = match used {
37            Some(parameter) => Some(parameter.as_ref().try_into()?),
38            _ => None,
39        };
40        let icon_ids = parameters.remove("EVENT_CONTEXT", "ICON_IDS");
41        let icon_ids = match icon_ids {
42            Some(parameter) => Some(parameter.as_ref().try_into()?),
43            _ => None,
44        };
45        let labels = parameters.remove("EVENT_CONTEXT", "LABELS");
46        let labels = match labels {
47            Some(parameter) => Some(parameter.as_ref().try_into()?),
48            _ => None,
49        };
50        let descriptions = parameters.remove("EVENT_CONTEXT", "DESCRIPTIONS");
51        let descriptions = match descriptions {
52            Some(parameter) => Some(parameter.as_ref().try_into()?),
53            _ => None,
54        };
55        Ok(EventContext {
56            used,
57            icon_ids,
58            labels,
59            descriptions,
60            colours: get_colour_array(parameters, "EVENT_CONTEXT", "COLOURS"),
61        })
62    }
63}
64
65fn get_colour_array(
66    parameters: &mut Parameters,
67    group_name: &str,
68    parameter_name: &str,
69) -> Option<Vec<[u8; 3]>> {
70    let parameter = parameters.remove(group_name, parameter_name)?;
71    match &parameter.data {
72        ParameterData::Byte(data) => {
73            if parameter.dimensions.len() == 2 {
74                let mut colours = Vec::new();
75                for row in 0..data.len() % 3 {
76                    let mut colour = [0; 3];
77                    colour[0] = data[row * 3];
78                    colour[1] = data[row * 3 + 1];
79                    colour[2] = data[row * 3 + 2];
80                    colours.push(colour);
81                }
82                Some(colours)
83            } else {
84                None
85            }
86        }
87        _ => None,
88    }
89}
90
91/// The `Events` struct contains the events from the C3D file header.
92#[derive(Debug, Clone, PartialEq, Default)]
93pub struct Events {
94    pub supports_events_labels: bool,
95    events: Vec<Event>,
96    event_context: EventContext,
97}
98
99impl ToString for Events {
100    fn to_string(&self) -> String {
101        let mut string = String::new();
102        string.push_str("Events:\n");
103        string.push_str(&format!(
104            "  Supports events labels: {}\n",
105            self.supports_events_labels
106        ));
107        string.push_str(&format!("  Number of events: {}\n", self.events.len()));
108        for event in &self.events {
109            string.push_str(&format!(
110                "  Event: {}\n",
111                event.id.iter().collect::<String>()
112            ));
113            string.push_str(&format!("    Label: {}\n", event.label));
114            string.push_str(&format!("    Display flag: {}\n", event.display_flag));
115            string.push_str(&format!("    Time: {}\n", event.time));
116            string.push_str(&format!("    Context: {}\n", event.context));
117            string.push_str(&format!("    Description: {}\n", event.description));
118            string.push_str(&format!("    Subject: {}\n", event.subject));
119            string.push_str(&format!("    Icon ID: {}\n", event.icon_id));
120            string.push_str(&format!("    Generic flag: {}\n", event.generic_flag));
121        }
122        string
123    }
124}
125
126impl Deref for Events {
127    type Target = Vec<Event>;
128
129    fn deref(&self) -> &Self::Target {
130        &self.events
131    }
132}
133
134impl DerefMut for Events {
135    fn deref_mut(&mut self) -> &mut Self::Target {
136        &mut self.events
137    }
138}
139
140/// The `Event` struct contains the information for a single event.
141#[derive(Debug, Clone, Default, PartialEq)]
142pub struct Event {
143    pub id: [char; 4], // found in header
144    pub label: String, // found in parameter section
145    pub display_flag: bool,
146    pub time: f32,
147    pub context: String,
148    pub description: String,
149    pub subject: String,
150    pub icon_id: i16,
151    pub generic_flag: i16,
152}
153
154#[allow(dead_code)]
155impl Event {
156    pub fn new() -> Event {
157        Event::default()
158    }
159}
160
161impl Events {
162    #[allow(dead_code)]
163    pub(crate) fn new() -> Events {
164        Events::default()
165    }
166
167    /// Returns the number of events in the C3D file header.
168    /// The maximum number of events is 18.
169    pub fn num_events(&self) -> usize {
170        self.events.len()
171    }
172
173    /// Returns the event at the specified index.
174    /// The index must be less than the number of events.
175    /// The maximum number of events is 18.
176    pub fn event(&self, index: usize) -> Option<&Event> {
177        if index < self.events.len() {
178            Some(&self.events[index])
179        } else {
180            None
181        }
182    }
183
184    pub(crate) fn from_header_and_parameters(
185        header_block: &[u8; 512],
186        parameters: &mut Parameters,
187        processor: &Processor,
188    ) -> Result<Events, C3dParseError> {
189        let supports_events_labels =
190            processor.u16([header_block[298], header_block[299]]) == 0x3039;
191        let num_time_events =
192            get_num_time_events(header_block, parameters, &processor, supports_events_labels)?;
193
194        let mut events = Vec::<Event>::with_capacity(num_time_events);
195
196        let times = get_times_array(parameters)?;
197        let labels = get_labels_array(parameters)?;
198        let contexts = get_contexts_array(parameters);
199        let descriptions = get_descriptions_array(parameters);
200        let subjects = get_subjects_array(parameters);
201        let icon_ids = get_icon_ids_array(parameters);
202        let generic_flags = get_generic_flags_array(parameters);
203
204        // event times start at byte 306
205        for event_num in 0..num_time_events {
206            let id = get_event_id(event_num, header_block)?;
207            let label = match supports_events_labels && labels.len() > event_num {
208                true => labels[event_num].clone(),
209                false => "".to_string(),
210            };
211            let display_flag = match supports_events_labels {
212                true => get_display_flag(event_num, header_block),
213                false => true,
214            };
215            let time = verify_time(event_num, header_block, &times, processor)?;
216            let context = get_event_context(event_num, &contexts);
217            let description = get_event_description(event_num, &descriptions);
218            let subject = get_event_subject(event_num, &subjects);
219            let icon_id = get_event_icon_id(event_num, &icon_ids);
220            let generic_flag = get_event_generic_flag(event_num, &generic_flags);
221            events.push(Event {
222                id,
223                label,
224                display_flag,
225                time,
226                context,
227                description,
228                subject,
229                icon_id,
230                generic_flag,
231            });
232        }
233        Ok(Events {
234            supports_events_labels,
235            events,
236            event_context: EventContext::from_parameters(parameters)?,
237        })
238    }
239
240    pub(crate) fn write(
241        &self,
242        processor: &Processor,
243        group_names_to_ids: &HashMap<String, usize>,
244    ) -> Result<Vec<u8>, C3dWriteError> {
245        let mut bytes = Vec::new();
246        bytes.extend(Parameter::integer(self.events.len() as i16).write(
247            processor,
248            "USED".to_string(),
249            group_names_to_ids["EVENT"],
250            false,
251        )?);
252        let times = self
253            .events
254            .iter()
255            .map(|event| event.time)
256            .collect::<Vec<f32>>();
257        if times.len() > 0 {
258            bytes.extend(Parameter::floats(times)?.write(
259                processor,
260                "TIMES".to_string(),
261                group_names_to_ids["EVENT"],
262                false,
263            )?);
264        }
265        let labels = self
266            .events
267            .iter()
268            .map(|event| event.label.clone())
269            .collect::<Vec<String>>();
270        if labels.len() > 0 {
271            bytes.extend(Parameter::strings(labels).write(
272                processor,
273                "LABELS".to_string(),
274                group_names_to_ids["EVENT"],
275                false,
276            )?);
277        }
278        let contexts = self
279            .events
280            .iter()
281            .map(|event| event.context.clone())
282            .collect::<Vec<String>>();
283        if contexts.len() > 0 {
284            bytes.extend(Parameter::strings(contexts).write(
285                processor,
286                "CONTEXTS".to_string(),
287                group_names_to_ids["EVENT"],
288                false,
289            )?);
290        }
291        let descriptions = self
292            .events
293            .iter()
294            .map(|event| event.description.clone())
295            .collect::<Vec<String>>();
296        if descriptions.len() > 0 {
297            bytes.extend(Parameter::strings(descriptions).write(
298                processor,
299                "DESCRIPTIONS".to_string(),
300                group_names_to_ids["EVENT"],
301                false,
302            )?);
303        }
304        let subjects = self
305            .events
306            .iter()
307            .map(|event| event.subject.clone())
308            .collect::<Vec<String>>();
309        if subjects.len() > 0 {
310            bytes.extend(Parameter::strings(subjects).write(
311                processor,
312                "SUBJECTS".to_string(),
313                group_names_to_ids["EVENT"],
314                false,
315            )?);
316        }
317        let icon_ids = self
318            .events
319            .iter()
320            .map(|event| event.icon_id)
321            .collect::<Vec<i16>>();
322        if icon_ids.len() > 0 {
323            bytes.extend(Parameter::integers(icon_ids)?.write(
324                processor,
325                "ICON_IDS".to_string(),
326                group_names_to_ids["EVENT"],
327                false,
328            )?);
329        }
330        let generic_flags = self
331            .events
332            .iter()
333            .map(|event| event.generic_flag)
334            .collect::<Vec<i16>>();
335        if generic_flags.len() > 0 {
336            bytes.extend(Parameter::integers(generic_flags)?.write(
337                processor,
338                "GENERIC_FLAGS".to_string(),
339                group_names_to_ids["EVENT"],
340                false,
341            )?);
342        }
343        let event_context_used = match &self.event_context.used {
344            Some(used) => *used,
345            _ => 0,
346        };
347        if event_context_used > 0 {
348            bytes.extend(Parameter::integer(event_context_used).write(
349                processor,
350                "USED".to_string(),
351                group_names_to_ids["EVENT_CONTEXT"],
352                false,
353            )?);
354        }
355        let event_context_icon_ids = match &self.event_context.icon_ids {
356            Some(icon_ids) => icon_ids.iter().map(|id| *id as i16).collect::<Vec<i16>>(),
357            _ => Vec::new(),
358        };
359        if event_context_icon_ids.len() > 0 {
360            bytes.extend(Parameter::integers(event_context_icon_ids)?.write(
361                processor,
362                "ICON_IDS".to_string(),
363                group_names_to_ids["EVENT_CONTEXT"],
364                false,
365            )?);
366        }
367        let event_context_labels = match &self.event_context.labels {
368            Some(labels) => labels.clone(),
369            _ => Vec::new(),
370        };
371        if event_context_labels.len() > 0 {
372            bytes.extend(Parameter::strings(event_context_labels).write(
373                processor,
374                "LABELS".to_string(),
375                group_names_to_ids["EVENT_CONTEXT"],
376                false,
377            )?);
378        }
379        let event_context_descriptions = match &self.event_context.descriptions {
380            Some(descriptions) => descriptions.clone(),
381            _ => Vec::new(),
382        };
383        if event_context_descriptions.len() > 0 {
384            bytes.extend(Parameter::strings(event_context_descriptions).write(
385                processor,
386                "DESCRIPTIONS".to_string(),
387                group_names_to_ids["EVENT_CONTEXT"],
388                false,
389            )?);
390        }
391        let event_context_colours = match &self.event_context.colours {
392            Some(colours) => colours.clone(),
393            _ => Vec::new(),
394        };
395        if event_context_colours.len() > 0 {
396            let mut colours_grid = Grid::new(0, 3);
397            for colour in event_context_colours {
398                colours_grid.push_row(colour.to_vec());
399            }
400            bytes.extend(Parameter::byte_grid(colours_grid).write(
401                processor,
402                "COLOURS".to_string(),
403                group_names_to_ids["EVENT_CONTEXT"],
404                false,
405            )?);
406        }
407        Ok(bytes)
408    }
409}
410
411fn get_num_time_events(
412    header_block: &[u8; 512],
413    parameters: &mut Parameters,
414    processor: &Processor,
415    supports_events_labels: bool,
416) -> Result<usize, C3dParseError> {
417    match supports_events_labels {
418        true => {
419            let num_time_events = processor.i16([header_block[300], header_block[301]]);
420
421            if num_time_events > 18 {
422                return Err(C3dParseError::TooManyEvents(num_time_events));
423            }
424            let parameter_num_time_events = parameters.remove("EVENT", "USED");
425            if parameter_num_time_events.is_some() {
426                let parameter_num_time_events: i16 =
427                    parameter_num_time_events.unwrap().as_ref().try_into()?;
428                if parameter_num_time_events != num_time_events as i16 {
429                    return Ok(parameter_num_time_events as usize);
430                } else {
431                    return Ok(num_time_events as usize);
432                }
433            }
434        }
435        false => {
436            let parameter_num_time_events = parameters.remove("EVENT", "USED");
437            if parameter_num_time_events.is_some() {
438                let parameter_num_time_events: i16 =
439                    parameter_num_time_events.unwrap().as_ref().try_into()?;
440                return Ok(parameter_num_time_events as usize);
441            }
442        }
443    }
444    Ok(0)
445}
446
447fn get_times_array(parameters: &mut Parameters) -> Result<Vec<[f32; 2]>, C3dParseError> {
448    let parameter = parameters.remove("EVENT", "TIMES");
449    if parameter.is_none() {
450        Ok(Vec::new())
451    } else {
452        let parameter = parameter.unwrap();
453        match &parameter.data {
454            ParameterData::Float(data) => {
455                if parameter.dimensions.len() == 2 && data.len() > 1 {
456                    let mut times = Vec::new();
457                    for row in 0..data.len() % 2 {
458                        let mut time = [0.0; 2];
459                        time[0] = data[row * 2];
460                        time[1] = data[row * 2 + 1];
461                        times.push(time);
462                    }
463                    Ok(times)
464                } else {
465                    Ok(Vec::new())
466                }
467            }
468            _ => Err(C3dParseError::InvalidParameterType(
469                "EVENT ".to_string() + "TIMES",
470            )),
471        }
472    }
473}
474
475fn get_labels_array(parameters: &mut Parameters) -> Result<Vec<String>, C3dParseError> {
476    let labels = parameters.remove("EVENT", "LABELS");
477    if labels.is_none() {
478        Ok(Vec::new())
479    } else {
480        let labels = labels.unwrap();
481        Ok(labels.as_ref().try_into()?)
482    }
483}
484
485fn get_contexts_array(parameters: &mut Parameters) -> Vec<String> {
486    let contexts = parameters.remove("EVENT", "CONTEXTS");
487    if contexts.is_none() {
488        Vec::new()
489    } else {
490        let contexts = contexts.unwrap();
491        contexts.as_ref().try_into().unwrap_or(Vec::new())
492    }
493}
494
495fn get_descriptions_array(parameters: &mut Parameters) -> Vec<String> {
496    let descriptions = parameters.remove("EVENT", "DESCRIPTIONS");
497    if descriptions.is_none() {
498        Vec::new()
499    } else {
500        let descriptions = descriptions.unwrap();
501        descriptions.as_ref().try_into().unwrap_or(Vec::new())
502    }
503}
504
505fn get_subjects_array(parameters: &mut Parameters) -> Vec<String> {
506    let subjects = parameters.remove("EVENT", "SUBJECTS");
507    if subjects.is_none() {
508        Vec::new()
509    } else {
510        let subjects = subjects.unwrap();
511        subjects.as_ref().try_into().unwrap_or(Vec::new())
512    }
513}
514
515fn get_icon_ids_array(parameters: &mut Parameters) -> Vec<i16> {
516    let icon_ids = parameters.remove("EVENT", "ICON_IDS");
517    if icon_ids.is_none() {
518        Vec::new()
519    } else {
520        let icon_ids = icon_ids.unwrap();
521        icon_ids.as_ref().try_into().unwrap_or(Vec::new())
522    }
523}
524
525fn get_generic_flags_array(parameters: &mut Parameters) -> Vec<i16> {
526    let generic_flags = parameters.remove("EVENT", "GENERIC_FLAGS");
527    if generic_flags.is_none() {
528        Vec::new()
529    } else {
530        let generic_flags = generic_flags.unwrap();
531        generic_flags.as_ref().try_into().unwrap_or(Vec::new())
532    }
533}
534
535fn verify_time(
536    event_num: usize,
537    header_block: &[u8; 512],
538    _time: &Vec<[f32; 2]>,
539    processor: &Processor,
540) -> Result<f32, C3dParseError> {
541    let time_start = 304 + (event_num * 4);
542    Ok(processor.f32(header_block[time_start..time_start + 4].try_into().unwrap()))
543    // TODO: use time
544}
545
546fn get_event_id(event_num: usize, header_block: &[u8; 512]) -> Result<[char; 4], C3dParseError> {
547    if event_num > 18 {
548        return Ok([0x00 as char; 4]);
549    }
550    let label_start = 396 + (event_num * 4);
551    let label_bytes: [u8; 4] = header_block[label_start..label_start + 4]
552        .try_into()
553        .unwrap();
554    let label_chars = label_bytes
555        .iter()
556        .map(|b| *b as char)
557        .collect::<Vec<char>>();
558    Ok(label_chars.try_into().unwrap())
559}
560
561fn get_display_flag(event_num: usize, header_block: &[u8; 512]) -> bool {
562    let display_flag_start = 376 + event_num;
563    header_block[display_flag_start] == 0
564}
565
566fn get_event_context(event_num: usize, contexts: &Vec<String>) -> String {
567    if contexts.len() <= event_num {
568        return "".to_string();
569    }
570    contexts[event_num].clone()
571}
572
573fn get_event_description(event_num: usize, descriptions: &Vec<String>) -> String {
574    if descriptions.len() <= event_num {
575        return "".to_string();
576    }
577    descriptions[event_num].clone()
578}
579
580fn get_event_subject(event_num: usize, subjects: &Vec<String>) -> String {
581    if subjects.len() <= event_num {
582        return "".to_string();
583    }
584    subjects[event_num].clone()
585}
586
587fn get_event_icon_id(event_num: usize, icon_ids: &Vec<i16>) -> i16 {
588    if icon_ids.len() <= event_num {
589        return 0;
590    }
591    icon_ids[event_num]
592}
593
594fn get_event_generic_flag(event_num: usize, generic_flags: &Vec<i16>) -> i16 {
595    if generic_flags.len() <= event_num {
596        return 0;
597    }
598    generic_flags[event_num]
599}