use ifc_model::{EntityId, Value};
use crate::error::{StyleError, StyleResult};
#[derive(Debug, Clone, PartialEq)]
pub struct TriangleTextureCoordinates {
triangles: Vec<[[f64; 2]; 3]>,
}
impl TriangleTextureCoordinates {
#[must_use]
pub fn covered(&self) -> usize {
self.triangles.len()
}
#[must_use]
pub fn triangle(&self, index: usize) -> Option<[[f64; 2]; 3]> {
self.triangles.get(index).copied()
}
#[must_use]
pub fn triangles(&self) -> &[[[f64; 2]; 3]] {
&self.triangles
}
}
pub(super) fn resolve(
map: EntityId,
map_type: &str,
tex_coord_index: &Value,
tex_coords_list: &Value,
triangle_count: usize,
) -> StyleResult<TriangleTextureCoordinates> {
let invalid = |attribute: &'static str, value: String| StyleError::InvalidValue {
entity: map_type.to_owned(),
id: map,
attribute,
value,
};
let coordinates: Vec<[f64; 2]> = tex_coords_list
.unwrap_typed()
.as_list()
.ok_or_else(|| invalid("TexCoordsList", "not a list".to_owned()))?
.iter()
.map(|row| {
let pair = row.unwrap_typed().as_list()?;
match pair {
[s, t] => Some([s.unwrap_typed().as_f64()?, t.unwrap_typed().as_f64()?]),
_ => None,
}
})
.collect::<Option<_>>()
.ok_or_else(|| invalid("TexCoordsList", "an entry is not an (s, t) pair".to_owned()))?;
let rows = tex_coord_index
.unwrap_typed()
.as_list()
.ok_or_else(|| invalid("TexCoordIndex", "not a list".to_owned()))?;
if rows.len() > triangle_count {
return Err(invalid(
"TexCoordIndex",
format!(
"{} triangle(s) but the face set declares {triangle_count}",
rows.len()
),
));
}
let mut triangles = Vec::with_capacity(rows.len());
for (position, row) in rows.iter().enumerate() {
let corners = row
.unwrap_typed()
.as_list()
.filter(|corners| corners.len() == 3)
.ok_or_else(|| {
invalid(
"TexCoordIndex",
format!("triangle {} does not have exactly 3 indices", position + 1),
)
})?;
let mut resolved = [[0.0; 2]; 3];
for (slot, corner) in corners.iter().enumerate() {
let one_based = corner.unwrap_typed().as_i64().unwrap_or(0);
let coordinate = usize::try_from(one_based)
.ok()
.and_then(|index| index.checked_sub(1))
.and_then(|index| coordinates.get(index))
.ok_or_else(|| {
invalid(
"TexCoordIndex",
format!(
"triangle {} addresses texture vertex {one_based}, \
but TexCoordsList has {}",
position + 1,
coordinates.len()
),
)
})?;
resolved[slot] = *coordinate;
}
triangles.push(resolved);
}
Ok(TriangleTextureCoordinates { triangles })
}
#[cfg(test)]
mod tests;