use ifc_lite_core::{DecodedEntity, EntityDecoder, EntityScanner, IfcType};
use rustc_hash::FxHashMap;
#[derive(Debug, Clone)]
pub struct MeshTexture {
pub rgba: Vec<u8>,
pub width: u32,
pub height: u32,
pub repeat_s: bool,
pub repeat_t: bool,
}
#[derive(Debug, Clone)]
pub struct ResolvedTextureMap {
pub texture: MeshTexture,
pub tex_coords: Vec<[f32; 2]>,
pub tex_coord_index: Vec<[u32; 3]>,
}
pub fn decode_step_binary(s: &str) -> Vec<u8> {
let s = s.trim().trim_matches('"');
if s.len() < 3 {
return Vec::new();
}
let hex = s.as_bytes();
let mut out = Vec::with_capacity(hex.len() / 2);
let mut i = 1;
while i + 1 < hex.len() {
match (hex_val(hex[i]), hex_val(hex[i + 1])) {
(Some(h), Some(l)) => out.push((h << 4) | l),
_ => break,
}
i += 2;
}
out
}
#[inline]
fn hex_val(b: u8) -> Option<u8> {
match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
}
}
const MAX_TEX_DIM: u32 = 16384;
fn decode_png(bytes: &[u8]) -> Option<(Vec<u8>, u32, u32)> {
let mut decoder = png::Decoder::new(bytes);
decoder.set_transformations(png::Transformations::EXPAND | png::Transformations::STRIP_16);
let mut reader = decoder.read_info().ok()?;
let png_info = reader.info();
if png_info.width == 0
|| png_info.height == 0
|| png_info.width > MAX_TEX_DIM
|| png_info.height > MAX_TEX_DIM
{
return None;
}
let mut buf = vec![0u8; reader.output_buffer_size()];
let info = reader.next_frame(&mut buf).ok()?;
let (w, h) = (info.width, info.height);
let px = (w as usize) * (h as usize);
let src = &buf[..info.buffer_size()];
let mut rgba = Vec::with_capacity(px * 4);
match info.color_type {
png::ColorType::Rgba => rgba.extend_from_slice(&src[..px * 4]),
png::ColorType::Rgb => {
for c in src.chunks_exact(3) {
rgba.extend_from_slice(&[c[0], c[1], c[2], 255]);
}
}
png::ColorType::Grayscale => {
for &g in src.iter() {
rgba.extend_from_slice(&[g, g, g, 255]);
}
}
png::ColorType::GrayscaleAlpha => {
for c in src.chunks_exact(2) {
rgba.extend_from_slice(&[c[0], c[0], c[0], c[1]]);
}
}
png::ColorType::Indexed => return None,
}
if rgba.len() != px * 4 {
return None;
}
Some((rgba, w, h))
}
fn decode_jpeg(bytes: &[u8]) -> Option<(Vec<u8>, u32, u32)> {
let mut decoder = jpeg_decoder::Decoder::new(bytes);
decoder.read_info().ok()?;
let info = decoder.info()?;
if info.width == 0
|| info.height == 0
|| info.width as u32 > MAX_TEX_DIM
|| info.height as u32 > MAX_TEX_DIM
{
return None;
}
let pixels = decoder.decode().ok()?;
let (w, h) = (info.width as usize, info.height as usize);
let px = w * h;
let mut rgba = Vec::with_capacity(px * 4);
match info.pixel_format {
jpeg_decoder::PixelFormat::RGB24 => {
for c in pixels.chunks_exact(3) {
rgba.extend_from_slice(&[c[0], c[1], c[2], 255]);
}
}
jpeg_decoder::PixelFormat::L8 => {
for &g in pixels.iter() {
rgba.extend_from_slice(&[g, g, g, 255]);
}
}
_ => return None,
}
if rgba.len() != px * 4 {
return None;
}
Some((rgba, w as u32, h as u32))
}
fn decode_raster_image(bytes: &[u8]) -> Option<(Vec<u8>, u32, u32)> {
const PNG_MAGIC: [u8; 8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
if bytes.len() >= 8 && bytes[..8] == PNG_MAGIC {
return decode_png(bytes);
}
if bytes.len() >= 3 && bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF {
return decode_jpeg(bytes);
}
None
}
fn decode_blob_texture(entity: &DecodedEntity) -> Option<MeshTexture> {
let raster_code = entity.get(6).and_then(|a| a.as_string())?;
let bytes = decode_step_binary(raster_code);
if bytes.len() < 8 {
return None;
}
let (rgba, width, height) = decode_raster_image(&bytes)?;
Some(MeshTexture {
rgba,
width,
height,
repeat_s: read_bool(entity, 0).unwrap_or(true),
repeat_t: read_bool(entity, 1).unwrap_or(true),
})
}
fn decode_pixel_texture(entity: &DecodedEntity) -> Option<MeshTexture> {
let width = entity.get(5).and_then(|a| a.as_int())?;
let height = entity.get(6).and_then(|a| a.as_int())?;
let components = entity.get(7).and_then(|a| a.as_int())?;
let max_dim = MAX_TEX_DIM as i64;
if width <= 0
|| height <= 0
|| width > max_dim
|| height > max_dim
|| !(1..=4).contains(&components)
{
return None;
}
let width = width as u32;
let height = height as u32;
let components = components as usize;
let pixels = entity.get(8).and_then(|a| a.as_list())?;
let expected = (width as usize) * (height as usize);
let mut rgba = Vec::with_capacity(expected * 4);
for px in pixels.iter() {
let s = px.as_string()?;
let comp = decode_step_binary(s);
if comp.len() < components {
return None;
}
let (r, g, b, a) = match components {
1 => (comp[0], comp[0], comp[0], 255),
2 => (comp[0], comp[0], comp[0], comp[1]),
3 => (comp[0], comp[1], comp[2], 255),
_ => (comp[0], comp[1], comp[2], comp[3]),
};
rgba.extend_from_slice(&[r, g, b, a]);
}
if rgba.len() != expected * 4 {
return None;
}
Some(MeshTexture {
rgba,
width,
height,
repeat_s: read_bool(entity, 0).unwrap_or(true),
repeat_t: read_bool(entity, 1).unwrap_or(true),
})
}
fn read_bool(entity: &DecodedEntity, idx: usize) -> Option<bool> {
entity.get(idx).and_then(|a| a.as_enum()).map(|v| v == "T")
}
fn resolve_surface_texture(texture_id: u32, decoder: &mut EntityDecoder) -> Option<MeshTexture> {
let entity = decoder.decode_by_id(texture_id).ok()?;
match entity.ifc_type {
IfcType::IfcBlobTexture => decode_blob_texture(&entity),
IfcType::IfcPixelTexture => decode_pixel_texture(&entity),
_ => None,
}
}
fn resolve_triangle_texture_map(
entity: &DecodedEntity,
decoder: &mut EntityDecoder,
) -> Option<(u32, ResolvedTextureMap)> {
let face_set_id = entity.get_ref(1)?;
let maps = entity.get(0)?.as_list()?;
let texture_id = maps.iter().find_map(|m| m.as_entity_ref())?;
let texture = resolve_surface_texture(texture_id, decoder)?;
let tvl_id = entity.get_ref(2)?;
let tvl = decoder.decode_by_id(tvl_id).ok()?;
let coord_list = tvl.get(0)?.as_list()?;
let tex_coords: Vec<[f32; 2]> = coord_list
.iter()
.map(|c| {
let uv = c.as_list()?;
let u = uv.first().and_then(|v| v.as_float())? as f32;
let v = uv.get(1).and_then(|v| v.as_float())? as f32;
Some([u, v])
})
.collect::<Option<Vec<_>>>()?;
if tex_coords.is_empty() {
return None;
}
let index_attr = entity.get(3)?.as_list()?;
let tex_coord_index: Vec<[u32; 3]> = index_attr
.iter()
.map(|tri| {
let t = tri.as_list()?;
let a = t.first().and_then(|v| v.as_int())? as u32;
let b = t.get(1).and_then(|v| v.as_int())? as u32;
let c = t.get(2).and_then(|v| v.as_int())? as u32;
Some([a, b, c])
})
.collect::<Option<Vec<_>>>()?;
if tex_coord_index.is_empty() {
return None;
}
Some((
face_set_id,
ResolvedTextureMap {
texture,
tex_coords,
tex_coord_index,
},
))
}
pub fn build_texture_index(
content: &str,
decoder: &mut EntityDecoder,
) -> FxHashMap<u32, ResolvedTextureMap> {
let mut index = FxHashMap::default();
if !content.contains("IFCINDEXEDTRIANGLETEXTUREMAP") {
return index;
}
let mut scanner = EntityScanner::new(content);
while let Some((id, type_name, start, end)) = scanner.next_entity() {
if type_name != "IFCINDEXEDTRIANGLETEXTUREMAP" {
continue;
}
if let Ok(entity) = decoder.decode_at_with_id(id, start, end) {
if let Some((face_set_id, resolved)) = resolve_triangle_texture_map(&entity, decoder) {
index.entry(face_set_id).or_insert(resolved);
}
}
}
index
}