ifc_lite_core/
fast_parse.rs1#[inline(always)]
15fn is_number_start(b: u8) -> bool {
16 b.is_ascii_digit() || b == b'-' || b == b'.'
17}
18
19#[inline]
21fn estimate_float_count(bytes: &[u8]) -> usize {
22 bytes.len() / 8
24}
25
26#[inline]
28fn estimate_int_count(bytes: &[u8]) -> usize {
29 bytes.len() / 4
31}
32
33#[inline]
45pub fn parse_coordinates_direct(bytes: &[u8]) -> Vec<f32> {
46 let mut result = Vec::with_capacity(estimate_float_count(bytes));
47 let mut pos = 0;
48 let len = bytes.len();
49
50 while pos < len {
51 while pos < len && !is_number_start(bytes[pos]) {
53 pos += 1;
54 }
55 if pos >= len {
56 break;
57 }
58
59 match fast_float::parse_partial::<f32, _>(&bytes[pos..]) {
61 Ok((value, consumed)) if consumed > 0 => {
62 result.push(value);
63 pos += consumed;
64 }
65 _ => {
66 pos += 1;
68 }
69 }
70 }
71
72 result
73}
74
75#[inline]
79pub fn parse_coordinates_direct_f64(bytes: &[u8]) -> Vec<f64> {
80 let mut result = Vec::with_capacity(estimate_float_count(bytes));
81 let mut pos = 0;
82 let len = bytes.len();
83
84 while pos < len {
85 while pos < len && !is_number_start(bytes[pos]) {
86 pos += 1;
87 }
88 if pos >= len {
89 break;
90 }
91
92 match fast_float::parse_partial::<f64, _>(&bytes[pos..]) {
93 Ok((value, consumed)) if consumed > 0 => {
94 result.push(value);
95 pos += consumed;
96 }
97 _ => {
98 pos += 1;
99 }
100 }
101 }
102
103 result
104}
105
106#[inline]
117pub fn parse_indices_direct(bytes: &[u8]) -> Vec<u32> {
118 let mut result = Vec::with_capacity(estimate_int_count(bytes));
119 let mut pos = 0;
120 let len = bytes.len();
121
122 while pos < len {
123 while pos < len && !bytes[pos].is_ascii_digit() {
125 pos += 1;
126 }
127 if pos >= len {
128 break;
129 }
130
131 let mut value: u32 = 0;
133 while pos < len && bytes[pos].is_ascii_digit() {
134 value = value.wrapping_mul(10).wrapping_add((bytes[pos] - b'0') as u32);
135 pos += 1;
136 }
137
138 result.push(value.saturating_sub(1));
140 }
141
142 result
143}
144
145#[inline]
152pub fn extract_coordinate_list_from_entity(bytes: &[u8]) -> Option<Vec<f32>> {
153 let start = memchr::memmem::find(bytes, b"((")?;
155
156 let end = memchr::memmem::rfind(bytes, b"))")?;
158
159 if end <= start {
160 return None;
161 }
162
163 Some(parse_coordinates_direct(&bytes[start..end + 2]))
165}
166
167#[inline]
173pub fn extract_face_indices_from_entity(bytes: &[u8]) -> Option<Vec<u32>> {
174 let mut paren_depth = 0;
177 let mut comma_count = 0;
178 let mut attr_start = None;
179 let mut attr_end = None;
180
181 for (i, &b) in bytes.iter().enumerate() {
182 match b {
183 b'(' => {
184 if paren_depth == 1 && comma_count == 3 && attr_start.is_none() {
185 attr_start = Some(i);
186 }
187 paren_depth += 1;
188 }
189 b')' => {
190 paren_depth -= 1;
191 if paren_depth == 1 && comma_count == 3 && attr_start.is_some() && attr_end.is_none() {
192 attr_end = Some(i + 1);
193 }
194 }
195 b',' if paren_depth == 1 => {
196 if comma_count == 3 && attr_end.is_none() && attr_start.is_some() {
197 attr_end = Some(i);
198 }
199 comma_count += 1;
200 if comma_count == 3 {
201 }
203 }
204 _ => {}
205 }
206 }
207
208 let start = attr_start?;
209 let end = attr_end?;
210
211 if end <= start {
212 return None;
213 }
214
215 Some(parse_indices_direct(&bytes[start..end]))
216}
217
218#[inline]
220pub fn should_use_fast_path(type_name: &str) -> bool {
221 matches!(
222 type_name.to_uppercase().as_str(),
223 "IFCCARTESIANPOINTLIST3D"
224 | "IFCTRIANGULATEDFACESET"
225 | "IFCPOLYGONALFACESET"
226 | "IFCINDEXEDPOLYGONALFACE"
227 )
228}
229
230#[inline]
234pub fn extract_entity_type_name(bytes: &[u8]) -> Option<&str> {
235 let eq_pos = bytes.iter().position(|&b| b == b'=')?;
237 let paren_pos = bytes[eq_pos..].iter().position(|&b| b == b'(')?;
239 let type_start = eq_pos + 1;
240 let type_end = eq_pos + paren_pos;
241
242 if type_end <= type_start {
243 return None;
244 }
245
246 std::str::from_utf8(&bytes[type_start..type_end]).ok()
247}
248
249#[inline]
253pub fn extract_first_entity_ref(bytes: &[u8]) -> Option<u32> {
254 let paren_pos = bytes.iter().position(|&b| b == b'(')?;
256 let content = &bytes[paren_pos + 1..];
257
258 let hash_pos = content.iter().position(|&b| b == b'#')?;
260 let id_start = hash_pos + 1;
261
262 let mut id: u32 = 0;
264 let mut i = id_start;
265 while i < content.len() && content[i].is_ascii_digit() {
266 id = id.wrapping_mul(10).wrapping_add((content[i] - b'0') as u32);
267 i += 1;
268 }
269
270 if i > id_start {
271 Some(id)
272 } else {
273 None
274 }
275}
276
277#[derive(Debug, Clone)]
279pub struct FastMeshData {
280 pub positions: Vec<f32>,
281 pub indices: Vec<u32>,
282}
283
284#[inline]
296pub fn process_triangulated_faceset_direct<F>(
297 faceset_bytes: &[u8],
298 get_entity_bytes: F,
299) -> Option<FastMeshData>
300where
301 F: Fn(u32) -> Option<Vec<u8>>,
302{
303 let coord_entity_id = extract_first_entity_ref(faceset_bytes)?;
305
306 let coord_bytes = get_entity_bytes(coord_entity_id)?;
308
309 let positions = parse_coordinates_direct(&coord_bytes);
311
312 let indices = extract_face_indices_from_entity(faceset_bytes)?;
314
315 Some(FastMeshData { positions, indices })
316}
317
318#[inline]
322pub fn extract_entity_refs_from_list(bytes: &[u8]) -> Vec<u32> {
323 let mut ids = Vec::with_capacity(16);
324 let mut i = 0;
325 let len = bytes.len();
326
327 while i < len {
328 while i < len && bytes[i] != b'#' {
330 i += 1;
331 }
332 if i >= len {
333 break;
334 }
335 i += 1; let mut id: u32 = 0;
339 while i < len && bytes[i].is_ascii_digit() {
340 id = id.wrapping_mul(10).wrapping_add((bytes[i] - b'0') as u32);
341 i += 1;
342 }
343 if id > 0 {
344 ids.push(id);
345 }
346 }
347
348 ids
349}
350
351#[cfg(test)]
352mod tests {
353 use super::*;
354
355 #[test]
356 fn test_parse_coordinates_direct() {
357 let bytes = b"((0.,0.,150.),(0.,40.,140.),(100.,0.,0.))";
358 let coords = parse_coordinates_direct(bytes);
359
360 assert_eq!(coords.len(), 9);
361 assert!((coords[0] - 0.0).abs() < 0.001);
362 assert!((coords[1] - 0.0).abs() < 0.001);
363 assert!((coords[2] - 150.0).abs() < 0.001);
364 assert!((coords[3] - 0.0).abs() < 0.001);
365 assert!((coords[4] - 40.0).abs() < 0.001);
366 assert!((coords[5] - 140.0).abs() < 0.001);
367 }
368
369 #[test]
370 fn test_parse_indices_direct() {
371 let bytes = b"((1,2,3),(2,1,4),(5,6,7))";
372 let indices = parse_indices_direct(bytes);
373
374 assert_eq!(indices.len(), 9);
375 assert_eq!(indices[0], 0); assert_eq!(indices[1], 1); assert_eq!(indices[2], 2); assert_eq!(indices[3], 1); assert_eq!(indices[4], 0); assert_eq!(indices[5], 3); }
383
384 #[test]
385 fn test_parse_scientific_notation() {
386 let bytes = b"((1.5E-10,2.0e+5,-3.14))";
387 let coords = parse_coordinates_direct(bytes);
388
389 assert_eq!(coords.len(), 3);
390 assert!((coords[0] - 1.5e-10).abs() < 1e-15);
391 assert!((coords[1] - 2.0e5).abs() < 1.0);
392 assert!((coords[2] - (-3.14)).abs() < 0.001);
393 }
394
395 #[test]
396 fn test_parse_negative_numbers() {
397 let bytes = b"((-1.0,-2.5,3.0))";
398 let coords = parse_coordinates_direct(bytes);
399
400 assert_eq!(coords.len(), 3);
401 assert!((coords[0] - (-1.0)).abs() < 0.001);
402 assert!((coords[1] - (-2.5)).abs() < 0.001);
403 assert!((coords[2] - 3.0).abs() < 0.001);
404 }
405
406 #[test]
407 fn test_extract_coordinate_list() {
408 let entity = b"#78=IFCCARTESIANPOINTLIST3D(((0.,0.,150.),(100.,0.,0.)));";
409 let coords = extract_coordinate_list_from_entity(entity).unwrap();
410
411 assert_eq!(coords.len(), 6);
412 assert!((coords[0] - 0.0).abs() < 0.001);
413 assert!((coords[2] - 150.0).abs() < 0.001);
414 assert!((coords[3] - 100.0).abs() < 0.001);
415 }
416
417 #[test]
418 fn test_should_use_fast_path() {
419 assert!(should_use_fast_path("IFCCARTESIANPOINTLIST3D"));
420 assert!(should_use_fast_path("IFCTRIANGULATEDFACESET"));
421 assert!(should_use_fast_path("IfcTriangulatedFaceSet"));
422 assert!(!should_use_fast_path("IFCWALL"));
423 assert!(!should_use_fast_path("IFCEXTRUDEDAREASOLID"));
424 }
425}