Skip to main content

semantic_scene/loader/mp3d/
mod.rs

1//! `Matterport3D` `.house` loader.
2use std::{collections::HashMap, io::BufRead};
3
4use crate::{
5    Category, ElementKind, LoadError, RegionCategory, Rotation3, SemanticLevel, SemanticObject,
6    SemanticRegion, SemanticScene, loader::SemanticSceneLoader,
7};
8
9mod category;
10pub(crate) mod parser;
11pub(crate) mod raw;
12
13use raw::Mp3dRecord;
14
15/// Loader for Habitat-Sim `Matterport3D` `.house` semantic descriptors.
16pub struct Mp3dLoader;
17
18/// Options for loading `Matterport3D` `.house` descriptors.
19#[derive(Debug, Clone, Default)]
20pub struct Mp3dOptions {
21    /// Rotation to apply while loading.
22    ///
23    /// Only [`Rotation3::Identity`] is supported in this milestone. Passing a
24    /// non-identity rotation returns [`LoadError::UnsupportedOption`].
25    pub rotation: Rotation3,
26}
27
28impl SemanticSceneLoader for Mp3dLoader {
29    type Options = Mp3dOptions;
30    type Error = LoadError;
31
32    fn from_reader<R: BufRead>(
33        mut reader: R,
34        options: Self::Options,
35    ) -> Result<SemanticScene, Self::Error> {
36        if options.rotation != Rotation3::Identity {
37            return Err(LoadError::UnsupportedOption("non-identity MP3D rotation"));
38        }
39
40        let mut header = String::new();
41        let bytes = reader.read_line(&mut header)?;
42        if bytes == 0 {
43            return Err(LoadError::BadHeader {
44                found: "<empty file>".to_string(),
45            });
46        }
47        let header = header.trim_end_matches(['\r', '\n']);
48        if header != "ASCII 1.1" {
49            return Err(LoadError::BadHeader {
50                found: header.to_string(),
51            });
52        }
53
54        let mut scene = SemanticScene::new();
55        for (offset, line) in reader.lines().enumerate() {
56            let line_number = offset + 2;
57            let line = line?;
58            if line.trim().is_empty() {
59                continue;
60            }
61
62            let record = parser::parse_record(&line).map_err(|source| LoadError::ParseLine {
63                line_number,
64                line: line.clone(),
65                source,
66            })?;
67            apply_record(&mut scene, record, line_number)?;
68        }
69
70        Ok(scene)
71    }
72}
73
74fn apply_record(
75    scene: &mut SemanticScene,
76    record: Mp3dRecord,
77    line_number: usize,
78) -> Result<(), LoadError> {
79    match record {
80        Mp3dRecord::House(record) => {
81            let counts = house_counts(&record);
82            scene.set_header(record.name, record.label, counts, record.aabb);
83        }
84        Mp3dRecord::Level(record) => {
85            scene.push_level(SemanticLevel::new(
86                record.index,
87                record.label,
88                record.position,
89                record.aabb,
90            ));
91        }
92        Mp3dRecord::Region(record) => apply_region(scene, &record, line_number)?,
93        Mp3dRecord::Category(record) => {
94            scene.push_category(Category::new(
95                record.index,
96                record.raw_index,
97                record.raw_name,
98                record.mpcat40_index,
99                record.mpcat40_name,
100            ));
101        }
102        Mp3dRecord::Object(record) => apply_object(scene, &record, line_number)?,
103        Mp3dRecord::Segment(record) => {
104            let object_index = scene.object_position_by_index(record.object_index).ok_or(
105                LoadError::MissingParent {
106                    line_number,
107                    kind: "object",
108                    index: record.object_index,
109                },
110            )?;
111            if scene
112                .insert_segment(record.segment_id, object_index)
113                .is_some()
114            {
115                return Err(LoadError::DuplicateSegmentId {
116                    line_number,
117                    segment_id: record.segment_id,
118                });
119            }
120        }
121        Mp3dRecord::Ignored => {}
122    }
123    Ok(())
124}
125
126fn house_counts(record: &raw::HouseRecord) -> HashMap<&'static str, usize> {
127    HashMap::from([
128        (ElementKind::Images.as_str(), record.images),
129        (ElementKind::Panoramas.as_str(), record.panoramas),
130        (ElementKind::Vertices.as_str(), record.vertices),
131        (ElementKind::Surfaces.as_str(), record.surfaces),
132        (ElementKind::Segments.as_str(), record.segments),
133        (ElementKind::Objects.as_str(), record.objects),
134        (ElementKind::Categories.as_str(), record.categories),
135        (ElementKind::Regions.as_str(), record.regions),
136        (ElementKind::Portals.as_str(), record.portals),
137        (ElementKind::Levels.as_str(), record.levels),
138    ])
139}
140
141fn apply_region(
142    scene: &mut SemanticScene,
143    record: &raw::RegionRecord,
144    line_number: usize,
145) -> Result<(), LoadError> {
146    let level_index =
147        if record.level_index < 0 {
148            None
149        } else {
150            Some(scene.level_position_by_index(record.level_index).ok_or(
151                LoadError::MissingParent {
152                    line_number,
153                    kind: "level",
154                    index: record.level_index,
155                },
156            )?)
157        };
158    let region_index = scene.regions().len();
159    scene.push_region(SemanticRegion::new(
160        record.index,
161        level_index,
162        RegionCategory::new(record.category_code),
163        record.position,
164        record.aabb,
165    ));
166    if let Some(level_index) = level_index
167        && let Some(level) = scene.level_mut(level_index)
168    {
169        level.add_region(region_index);
170    }
171    Ok(())
172}
173
174fn apply_object(
175    scene: &mut SemanticScene,
176    record: &raw::ObjectRecord,
177    line_number: usize,
178) -> Result<(), LoadError> {
179    let region_index = if record.region_index < 0 {
180        None
181    } else {
182        Some(scene.region_position_by_index(record.region_index).ok_or(
183            LoadError::MissingParent {
184                line_number,
185                kind: "region",
186                index: record.region_index,
187            },
188        )?)
189    };
190    let category_index = if record.category_index < 0 {
191        None
192    } else {
193        Some(
194            scene
195                .category_position_by_index(record.category_index)
196                .ok_or(LoadError::MissingCategory {
197                    line_number,
198                    index: record.category_index,
199                })?,
200        )
201    };
202
203    let object_index = scene.objects().len();
204    scene.push_object(SemanticObject::new(
205        record.index,
206        region_index,
207        category_index,
208        record.obb,
209    ));
210    if let Some(region_index) = region_index {
211        let level_index = scene.regions()[region_index].level_index();
212        if let Some(region) = scene.region_mut(region_index) {
213            region.add_object(object_index);
214        }
215        if let Some(level_index) = level_index
216            && let Some(level) = scene.level_mut(level_index)
217        {
218            level.add_object(object_index);
219        }
220    }
221    Ok(())
222}