semantic_scene/loader/mp3d/
mod.rs1use 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
15pub struct Mp3dLoader;
17
18#[derive(Debug, Clone, Default)]
20pub struct Mp3dOptions {
21 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}