ifc_lite_geometry/processors/
texture.rs1use ifc_lite_core::{DecodedEntity, EntityDecoder, EntityScanner, IfcType};
19use rustc_hash::FxHashMap;
20
21#[derive(Debug, Clone)]
23pub struct MeshTexture {
24 pub rgba: Vec<u8>,
26 pub width: u32,
27 pub height: u32,
28 pub repeat_s: bool,
30 pub repeat_t: bool,
31}
32
33#[derive(Debug, Clone)]
35pub struct ResolvedTextureMap {
36 pub texture: MeshTexture,
37 pub tex_coords: Vec<[f32; 2]>,
39 pub tex_coord_index: Vec<[u32; 3]>,
42}
43
44pub fn decode_step_binary(s: &str) -> Vec<u8> {
58 let s = s.trim().trim_matches('"');
59 if s.len() < 3 {
60 return Vec::new();
61 }
62 let hex = s.as_bytes();
63 let mut out = Vec::with_capacity(hex.len() / 2);
64 let mut i = 1;
66 while i + 1 < hex.len() {
67 match (hex_val(hex[i]), hex_val(hex[i + 1])) {
68 (Some(h), Some(l)) => out.push((h << 4) | l),
69 _ => break,
70 }
71 i += 2;
72 }
73 out
74}
75
76#[inline]
77fn hex_val(b: u8) -> Option<u8> {
78 match b {
79 b'0'..=b'9' => Some(b - b'0'),
80 b'a'..=b'f' => Some(b - b'a' + 10),
81 b'A'..=b'F' => Some(b - b'A' + 10),
82 _ => None,
83 }
84}
85
86const MAX_TEX_DIM: u32 = 16384;
91
92fn decode_png(bytes: &[u8]) -> Option<(Vec<u8>, u32, u32)> {
94 let mut decoder = png::Decoder::new(std::io::Cursor::new(bytes));
97 decoder.set_transformations(png::Transformations::EXPAND | png::Transformations::STRIP_16);
100 let mut reader = decoder.read_info().ok()?;
101 let png_info = reader.info();
103 if png_info.width == 0
104 || png_info.height == 0
105 || png_info.width > MAX_TEX_DIM
106 || png_info.height > MAX_TEX_DIM
107 {
108 return None;
109 }
110 let mut buf = vec![0u8; reader.output_buffer_size()?];
112 let info = reader.next_frame(&mut buf).ok()?;
113 let (w, h) = (info.width, info.height);
114 let px = (w as usize) * (h as usize);
115 let src = &buf[..info.buffer_size()];
116 let mut rgba = Vec::with_capacity(px * 4);
117 match info.color_type {
118 png::ColorType::Rgba => rgba.extend_from_slice(&src[..px * 4]),
119 png::ColorType::Rgb => {
120 for c in src.chunks_exact(3) {
121 rgba.extend_from_slice(&[c[0], c[1], c[2], 255]);
122 }
123 }
124 png::ColorType::Grayscale => {
125 for &g in src.iter() {
126 rgba.extend_from_slice(&[g, g, g, 255]);
127 }
128 }
129 png::ColorType::GrayscaleAlpha => {
130 for c in src.chunks_exact(2) {
131 rgba.extend_from_slice(&[c[0], c[0], c[0], c[1]]);
132 }
133 }
134 png::ColorType::Indexed => return None,
136 }
137 if rgba.len() != px * 4 {
138 return None;
139 }
140 Some((rgba, w, h))
141}
142
143fn decode_jpeg(bytes: &[u8]) -> Option<(Vec<u8>, u32, u32)> {
145 let mut decoder = jpeg_decoder::Decoder::new(bytes);
146 decoder.read_info().ok()?;
149 let info = decoder.info()?;
150 if info.width == 0
151 || info.height == 0
152 || info.width as u32 > MAX_TEX_DIM
153 || info.height as u32 > MAX_TEX_DIM
154 {
155 return None;
156 }
157 let pixels = decoder.decode().ok()?;
158 let (w, h) = (info.width as usize, info.height as usize);
159 let px = w * h;
160 let mut rgba = Vec::with_capacity(px * 4);
161 match info.pixel_format {
162 jpeg_decoder::PixelFormat::RGB24 => {
163 for c in pixels.chunks_exact(3) {
164 rgba.extend_from_slice(&[c[0], c[1], c[2], 255]);
165 }
166 }
167 jpeg_decoder::PixelFormat::L8 => {
168 for &g in pixels.iter() {
169 rgba.extend_from_slice(&[g, g, g, 255]);
170 }
171 }
172 _ => return None,
174 }
175 if rgba.len() != px * 4 {
176 return None;
177 }
178 Some((rgba, w as u32, h as u32))
179}
180
181fn decode_raster_image(bytes: &[u8]) -> Option<(Vec<u8>, u32, u32)> {
184 const PNG_MAGIC: [u8; 8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
185 if bytes.len() >= 8 && bytes[..8] == PNG_MAGIC {
186 return decode_png(bytes);
187 }
188 if bytes.len() >= 3 && bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF {
190 return decode_jpeg(bytes);
191 }
192 None
193}
194
195fn decode_blob_texture(entity: &DecodedEntity) -> Option<MeshTexture> {
199 let raster_code = entity.get(6).and_then(|a| a.as_string())?;
200 let bytes = decode_step_binary(raster_code);
201 if bytes.len() < 8 {
202 return None;
203 }
204 let (rgba, width, height) = decode_raster_image(&bytes)?;
207 Some(MeshTexture {
208 rgba,
209 width,
210 height,
211 repeat_s: read_bool(entity, 0).unwrap_or(true),
212 repeat_t: read_bool(entity, 1).unwrap_or(true),
213 })
214}
215
216fn decode_pixel_texture(entity: &DecodedEntity) -> Option<MeshTexture> {
220 let width = entity.get(5).and_then(|a| a.as_int())?;
224 let height = entity.get(6).and_then(|a| a.as_int())?;
225 let components = entity.get(7).and_then(|a| a.as_int())?;
226 let max_dim = MAX_TEX_DIM as i64;
227 if width <= 0
228 || height <= 0
229 || width > max_dim
230 || height > max_dim
231 || !(1..=4).contains(&components)
232 {
233 return None;
234 }
235 let width = width as u32;
236 let height = height as u32;
237 let components = components as usize;
238 let pixels = entity.get(8).and_then(|a| a.as_list())?;
239 let expected = (width as usize) * (height as usize);
240 let mut rgba = Vec::with_capacity(expected * 4);
241 for px in pixels.iter() {
242 let s = px.as_string()?;
243 let comp = decode_step_binary(s);
244 if comp.len() < components {
245 return None;
246 }
247 let (r, g, b, a) = match components {
249 1 => (comp[0], comp[0], comp[0], 255),
250 2 => (comp[0], comp[0], comp[0], comp[1]),
251 3 => (comp[0], comp[1], comp[2], 255),
252 _ => (comp[0], comp[1], comp[2], comp[3]),
253 };
254 rgba.extend_from_slice(&[r, g, b, a]);
255 }
256 if rgba.len() != expected * 4 {
257 return None;
258 }
259 Some(MeshTexture {
260 rgba,
261 width,
262 height,
263 repeat_s: read_bool(entity, 0).unwrap_or(true),
264 repeat_t: read_bool(entity, 1).unwrap_or(true),
265 })
266}
267
268fn read_bool(entity: &DecodedEntity, idx: usize) -> Option<bool> {
269 entity.get(idx).and_then(|a| a.as_enum()).map(|v| v == "T")
270}
271
272fn resolve_surface_texture(texture_id: u32, decoder: &mut EntityDecoder) -> Option<MeshTexture> {
274 let entity = decoder.decode_by_id(texture_id).ok()?;
275 match entity.ifc_type {
276 IfcType::IfcBlobTexture => decode_blob_texture(&entity),
277 IfcType::IfcPixelTexture => decode_pixel_texture(&entity),
278 _ => None,
280 }
281}
282
283fn resolve_triangle_texture_map(
288 entity: &DecodedEntity,
289 decoder: &mut EntityDecoder,
290) -> Option<(u32, ResolvedTextureMap)> {
291 let face_set_id = entity.get_ref(1)?;
292
293 let maps = entity.get(0)?.as_list()?;
295 let texture_id = maps.iter().find_map(|m| m.as_entity_ref())?;
296 let texture = resolve_surface_texture(texture_id, decoder)?;
297
298 let tvl_id = entity.get_ref(2)?;
304 let tvl = decoder.decode_by_id(tvl_id).ok()?;
305 let coord_list = tvl.get(0)?.as_list()?;
306 let tex_coords: Vec<[f32; 2]> = coord_list
307 .iter()
308 .map(|c| {
309 let uv = c.as_list()?;
310 let u = uv.first().and_then(|v| v.as_float())? as f32;
311 let v = uv.get(1).and_then(|v| v.as_float())? as f32;
312 Some([u, v])
313 })
314 .collect::<Option<Vec<_>>>()?;
315 if tex_coords.is_empty() {
316 return None;
317 }
318
319 let index_attr = entity.get(3)?.as_list()?;
322 let tex_coord_index: Vec<[u32; 3]> = index_attr
323 .iter()
324 .map(|tri| {
325 let t = tri.as_list()?;
326 let a = t.first().and_then(|v| v.as_int())? as u32;
327 let b = t.get(1).and_then(|v| v.as_int())? as u32;
328 let c = t.get(2).and_then(|v| v.as_int())? as u32;
329 Some([a, b, c])
330 })
331 .collect::<Option<Vec<_>>>()?;
332 if tex_coord_index.is_empty() {
333 return None;
334 }
335
336 Some((
337 face_set_id,
338 ResolvedTextureMap {
339 texture,
340 tex_coords,
341 tex_coord_index,
342 },
343 ))
344}
345
346pub fn build_texture_index(
350 content: &[u8],
351 decoder: &mut EntityDecoder,
352) -> FxHashMap<u32, ResolvedTextureMap> {
353 let mut index = FxHashMap::default();
354 if !content
355 .windows(b"IFCINDEXEDTRIANGLETEXTUREMAP".len())
356 .any(|window| window == b"IFCINDEXEDTRIANGLETEXTUREMAP")
357 {
358 return index;
359 }
360 let mut scanner = EntityScanner::new(content);
361 while let Some((id, type_name, start, end)) = scanner.next_entity() {
362 if type_name != "IFCINDEXEDTRIANGLETEXTUREMAP" {
363 continue;
364 }
365 if let Ok(entity) = decoder.decode_at_with_id(id, start, end) {
366 if let Some((face_set_id, resolved)) = resolve_triangle_texture_map(&entity, decoder) {
367 index.entry(face_set_id).or_insert(resolved);
368 }
369 }
370 }
371 index
372}