use std::collections::HashMap;
use std::convert::TryFrom;
use std::num::ParseFloatError;
use std::num::ParseIntError;
use crate::OwnedObject;
use fbxscii::ElementAttribute;
use super::AttrExtractor;
use super::AttrExtractorExt;
use super::{FbxObjectTag, FbxTryFromReason, FbxTypeMismatch, fbx_object_tag};
const MAX_UV_CHANNELS: usize = 8;
const MAX_COLOR_SETS: usize = 8;
const ATTR_MAPPING_INFORMATION_TYPE: &str = "MappingInformationType";
const ATTR_REFERENCE_INFORMATION_TYPE: &str = "ReferenceInformationType";
const MAPPING_BY_VERTICE: &str = "ByVertice";
const MAPPING_BY_POLYGON_VERTEX: &str = "ByPolygonVertex";
const MAPPING_BY_POLYGON: &str = "ByPolygon";
const MAPPING_ALL_SAME: &str = "AllSame";
const REFERENCE_DIRECT: &str = "Direct";
const REFERENCE_INDEX_TO_DIRECT: &str = "IndexToDirect";
const ATTR_VERTICES: &str = "Vertices";
const ATTR_POLYGON_VERTEX_INDEX: &str = "PolygonVertexIndex";
const ATTR_LAYER_ELEMENT_NORMAL: &str = "LayerElementNormal";
const ATTR_LAYER_ELEMENT_TANGENT: &str = "LayerElementTangent";
const ATTR_LAYER_ELEMENT_BINORMAL: &str = "LayerElementBinormal";
const ATTR_LAYER_ELEMENT_UV: &str = "LayerElementUV";
const ATTR_MATERIALS: &str = "Materials";
const ACCESSOR_KEY: &str = "a";
#[derive(Debug, PartialEq)]
pub struct MeshGeometry {
pub object: OwnedObject,
pub vertices: Vec<[f32; 3]>,
pub face_vertex_counts: Vec<u32>,
pub normals: Vec<[f32; 3]>,
pub tangents: Vec<[f32; 3]>,
pub binormals: Vec<[f32; 3]>,
pub texture_coords: [Vec<[f32; 2]>; MAX_UV_CHANNELS],
pub texture_coord_names: [String; MAX_UV_CHANNELS],
pub vertex_colors: [Vec<[f32; 4]>; MAX_COLOR_SETS],
pub material_indices: Vec<i32>,
}
impl MeshGeometry {
pub fn inner(&self) -> &OwnedObject {
&self.object
}
pub fn into_inner(self) -> OwnedObject {
self.object
}
}
impl TryFrom<OwnedObject> for MeshGeometry {
type Error = FbxTypeMismatch;
fn try_from(o: OwnedObject) -> Result<Self, Self::Error> {
match fbx_object_tag(&o) {
FbxObjectTag::MeshGeometry => {}
_ => {
return Err(FbxTypeMismatch::wrong_object_kind(
o,
"MeshGeometry".to_string(),
));
}
}
let attrs = &o.attributes;
let verts_attr = match attrs.extract_case_insensitive(ATTR_VERTICES) {
Some(a) => a,
None => {
return Err(FbxTypeMismatch::new(
o,
FbxTryFromReason::MissingAttribute {
name: ATTR_VERTICES.to_string(),
},
));
}
};
let vertices_result = parse_f32_array(verts_attr);
let Ok(vertices) = vertices_result else {
return Err(FbxTypeMismatch::new(
o,
FbxTryFromReason::InvalidAttributeFormat {
name: ATTR_VERTICES.to_string(),
detail: format!("invalid float token: {}", vertices_result.unwrap_err()),
},
));
};
let vertices = vertices
.chunks_exact(3)
.map(|c| [c[0], c[1], c[2]])
.collect::<Vec<[f32; 3]>>();
let poly_attr = match attrs.extract_case_insensitive(ATTR_POLYGON_VERTEX_INDEX) {
Some(a) => a,
None => {
return Err(FbxTypeMismatch::new(
o,
FbxTryFromReason::MissingAttribute {
name: ATTR_POLYGON_VERTEX_INDEX.to_string(),
},
));
}
};
let temp_faces_result = parse_i32_array(poly_attr);
let Ok(temp_faces) = temp_faces_result else {
return Err(FbxTypeMismatch::new(
o,
FbxTryFromReason::InvalidAttributeFormat {
name: ATTR_POLYGON_VERTEX_INDEX.to_string(),
detail: format!("invalid int token: {}", temp_faces_result.unwrap_err()),
},
));
};
let (vertices, face_vertex_counts, mapping_counts, mapping_offsets, mappings) =
match expand_mesh_polygon_vertices(&vertices, &temp_faces) {
Ok(v) => v,
Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
};
let vertex_count = vertices.len();
let mut normals = Vec::new();
if let Some(el) = attrs.extract_case_insensitive(ATTR_LAYER_ELEMENT_NORMAL) {
let map = el.get_children_distinct();
let mapping_ty = match map
.require_token_case_insensitive(ATTR_MAPPING_INFORMATION_TYPE)
.map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
{
Ok(s) => s,
Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
};
let reference_ty = match map
.require_token_case_insensitive(ATTR_REFERENCE_INFORMATION_TYPE)
.map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
{
Ok(s) => s,
Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
};
let normals_flat = match resolve_flat_f32_channel(
&map,
ResolveFlatF32ChannelParams {
data_name: "Normals",
index_name: "NormalsIndex",
vertex_count,
components: 3,
mapping_counts: &mapping_counts,
mapping_offsets: &mapping_offsets,
mappings: &mappings,
mapping_ty: mapping_ty,
reference_ty: reference_ty,
},
) {
Ok(v) => v,
Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
};
normals = normals_flat
.chunks_exact(3)
.map(|c| [c[0], c[1], c[2]])
.collect();
}
let mut tangents = Vec::new();
if let Some(el) = attrs.extract_case_insensitive(ATTR_LAYER_ELEMENT_TANGENT) {
let map = el.get_children_distinct();
let (data_name, index_name) = if map.extract_case_insensitive("Tangents").is_some() {
("Tangents", "TangentsIndex")
} else {
("Tangent", "TangentIndex")
};
let mapping_ty = match map
.require_token_case_insensitive(ATTR_MAPPING_INFORMATION_TYPE)
.map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
{
Ok(s) => s,
Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
};
let reference_ty = match map
.require_token_case_insensitive(ATTR_REFERENCE_INFORMATION_TYPE)
.map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
{
Ok(s) => s,
Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
};
let tangents_flat = match resolve_flat_f32_channel(
&map,
ResolveFlatF32ChannelParams {
data_name,
index_name,
vertex_count,
components: 3,
mapping_counts: &mapping_counts,
mapping_offsets: &mapping_offsets,
mappings: &mappings,
mapping_ty: mapping_ty,
reference_ty: reference_ty,
},
) {
Ok(v) => v,
Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
};
tangents = tangents_flat
.chunks_exact(3)
.map(|c| [c[0], c[1], c[2]])
.collect();
}
let mut binormals = Vec::new();
if let Some(el) = attrs.extract_case_insensitive(ATTR_LAYER_ELEMENT_BINORMAL) {
let map = el.get_children_distinct();
let (data_name, index_name) = if map.extract_case_insensitive("Binormals").is_some() {
("Binormals", "BinormalsIndex")
} else {
("Binormal", "BinormalIndex")
};
let mapping_ty = match map
.require_token_case_insensitive(ATTR_MAPPING_INFORMATION_TYPE)
.map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
{
Ok(s) => s,
Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
};
let reference_ty = match map
.require_token_case_insensitive(ATTR_REFERENCE_INFORMATION_TYPE)
.map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
{
Ok(s) => s,
Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
};
let binormals_flat = match resolve_flat_f32_channel(
&map,
ResolveFlatF32ChannelParams {
data_name,
index_name,
vertex_count,
components: 3,
mapping_counts: &mapping_counts,
mapping_offsets: &mapping_offsets,
mappings: &mappings,
mapping_ty: mapping_ty,
reference_ty: reference_ty,
},
) {
Ok(v) => v,
Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
};
binormals = binormals_flat
.chunks_exact(3)
.map(|c| [c[0], c[1], c[2]])
.collect();
}
let mut texture_coords: [Vec<[f32; 2]>; MAX_UV_CHANNELS] = Default::default();
let mut texture_coord_names: [String; MAX_UV_CHANNELS] = Default::default();
if let Some(el) = attrs.extract_case_insensitive(ATTR_LAYER_ELEMENT_UV) {
let map = el.get_children_distinct();
if let Ok(Some(name)) = map.optional_token_case_insensitive("Name") {
texture_coord_names[0] = name
.trim()
.trim_matches(|c| c == '"' || c == '\'')
.to_string();
}
let mapping_ty = match map
.require_token_case_insensitive(ATTR_MAPPING_INFORMATION_TYPE)
.map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
{
Ok(s) => s,
Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
};
let reference_ty = match map
.require_token_case_insensitive(ATTR_REFERENCE_INFORMATION_TYPE)
.map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
{
Ok(s) => s,
Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
};
let uv_flat = match resolve_flat_f32_channel(
&map,
ResolveFlatF32ChannelParams {
data_name: "UV",
index_name: "UVIndex",
vertex_count,
components: 2,
mapping_counts: &mapping_counts,
mapping_offsets: &mapping_offsets,
mappings: &mappings,
mapping_ty: mapping_ty,
reference_ty: reference_ty,
},
) {
Ok(v) => v,
Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
};
texture_coords[0] = uv_flat.chunks_exact(2).map(|c| [c[0], c[1]]).collect();
}
let mut vertex_colors: [Vec<[f32; 4]>; MAX_COLOR_SETS] = Default::default();
if let Some(el) = attrs.extract_case_insensitive("LayerElementColor") {
let map = el.get_children_distinct();
let mapping_ty = match map
.require_token_case_insensitive(ATTR_MAPPING_INFORMATION_TYPE)
.map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
{
Ok(s) => s,
Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
};
let reference_ty = match map
.require_token_case_insensitive(ATTR_REFERENCE_INFORMATION_TYPE)
.map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
{
Ok(s) => s,
Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
};
let colors_flat = match resolve_flat_f32_channel(
&map,
ResolveFlatF32ChannelParams {
data_name: "Colors",
index_name: "ColorIndex",
vertex_count,
components: 4,
mapping_counts: &mapping_counts,
mapping_offsets: &mapping_offsets,
mappings: &mappings,
mapping_ty: mapping_ty,
reference_ty: reference_ty,
},
) {
Ok(v) => v,
Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
};
vertex_colors[0] = colors_flat
.chunks_exact(4)
.map(|c| [c[0], c[1], c[2], c[3]])
.collect();
}
let material_indices =
if let Some(el) = attrs.extract_case_insensitive("LayerElementMaterial") {
let map = el.get_children_distinct();
let mapping_ty = match map
.require_token_case_insensitive(ATTR_MAPPING_INFORMATION_TYPE)
.map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
{
Ok(s) => s,
Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
};
let reference_ty = match map
.require_token_case_insensitive(ATTR_REFERENCE_INFORMATION_TYPE)
.map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
{
Ok(s) => s,
Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
};
match read_vertex_data_materials(
&map,
&face_vertex_counts,
vertex_count,
&mapping_ty,
&reference_ty,
) {
Ok(v) => v,
Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
}
} else {
Vec::new()
};
Ok(MeshGeometry {
object: o,
vertices,
face_vertex_counts,
normals,
tangents,
binormals,
texture_coords,
texture_coord_names,
vertex_colors,
material_indices,
})
}
}
fn parse_f32_array(attr: &ElementAttribute) -> Result<Vec<f32>, ParseFloatError> {
let children = attr.get_children_distinct();
let payload = children.get(ACCESSOR_KEY).unwrap_or(attr);
let tokens = payload.get_tokens();
tokens
.iter()
.flat_map(|t| t.split(','))
.map(|t| t.trim())
.filter(|t| !t.is_empty())
.map(|t| t.parse::<f32>())
.collect()
}
fn parse_i32_array(attr: &ElementAttribute) -> Result<Vec<i32>, ParseIntError> {
let children = attr.get_children_distinct();
let payload = children.get(ACCESSOR_KEY).unwrap_or(attr);
let tokens = payload.get_tokens();
tokens
.iter()
.flat_map(|t| t.split(','))
.map(|t| t.trim())
.filter(|t| !t.is_empty())
.map(|t| t.parse::<i32>())
.collect()
}
fn expand_mesh_polygon_vertices(
temp_verts: &[[f32; 3]],
temp_faces: &[i32],
) -> Result<(Vec<[f32; 3]>, Vec<u32>, Vec<u32>, Vec<u32>, Vec<u32>), FbxTryFromReason> {
let vertex_count = temp_verts.len();
let mut mapping_counts = vec![0u32; vertex_count];
let mut expanded_vertices = Vec::new();
let mut face_vertex_counts = Vec::new();
let mut count = 0u32;
for &index in temp_faces {
let absi = if index < 0 {
(-index - 1) as usize
} else {
index as usize
};
if absi >= vertex_count {
return Err(FbxTryFromReason::InvalidAttributeFormat {
name: ATTR_POLYGON_VERTEX_INDEX.to_string(),
detail: format!("index {absi} out of range (vertex count {vertex_count})"),
});
}
expanded_vertices.push(temp_verts[absi]);
count += 1;
mapping_counts[absi] = mapping_counts[absi].saturating_add(1);
if index < 0 {
face_vertex_counts.push(count);
count = 0;
}
}
let polygon_vertex_count = expanded_vertices.len();
let mut mapping_offsets = vec![0u32; vertex_count];
let mut cursor = 0u32;
for i in 0..vertex_count {
mapping_offsets[i] = cursor;
cursor += mapping_counts[i];
mapping_counts[i] = 0;
}
let mut mappings = vec![0u32; polygon_vertex_count];
cursor = 0;
for &index in temp_faces {
let absi = if index < 0 {
(-index - 1) as usize
} else {
index as usize
};
let slot = mapping_offsets[absi] + mapping_counts[absi];
mapping_counts[absi] += 1;
mappings[slot as usize] = cursor;
cursor += 1;
}
Ok((
expanded_vertices,
face_vertex_counts,
mapping_counts,
mapping_offsets,
mappings,
))
}
pub struct ResolveFlatF32ChannelParams<'a> {
data_name: &'a str,
index_name: &'a str,
vertex_count: usize,
components: usize,
mapping_counts: &'a [u32],
mapping_offsets: &'a [u32],
mappings: &'a [u32],
mapping_ty: &'a str,
reference_ty: &'a str,
}
fn resolve_flat_f32_channel(
source: &HashMap<String, ElementAttribute>,
params: ResolveFlatF32ChannelParams<'_>,
) -> Result<Vec<f32>, FbxTryFromReason> {
let mut is_direct = params.reference_ty.eq_ignore_ascii_case(REFERENCE_DIRECT);
let mut is_index_to_direct = params
.reference_ty
.eq_ignore_ascii_case(REFERENCE_INDEX_TO_DIRECT);
let has_data = source.extract_case_insensitive(params.data_name).is_some();
let has_index = source.extract_case_insensitive(params.index_name).is_some();
if is_index_to_direct && !has_index {
is_direct = true;
is_index_to_direct = false;
}
if params.components == 0 {
return Ok(Vec::new());
}
let mut vertex_out = vec![0f32; params.vertex_count * params.components];
let by_vertice = params.mapping_ty.eq_ignore_ascii_case(MAPPING_BY_VERTICE);
let by_polygon_vertex = params
.mapping_ty
.eq_ignore_ascii_case(MAPPING_BY_POLYGON_VERTEX);
if by_vertice && is_direct {
if !has_data {
return Ok(Vec::new());
}
let channel_attribute = source.extract_case_insensitive(params.data_name).ok_or(
FbxTryFromReason::InvalidAttributeFormat {
name: params.data_name.to_string(),
detail: "data channel not found".to_string(),
},
)?;
let channel_data_result = parse_f32_array(&channel_attribute);
let Ok(channel_data) = channel_data_result else {
return Err(FbxTryFromReason::InvalidAttributeFormat {
name: params.data_name.to_string(),
detail: format!("invalid float token: {}", channel_data_result.unwrap_err()),
});
};
if channel_data.len() != params.mapping_offsets.len() * params.components {
return Err(FbxTryFromReason::InvalidAttributeFormat {
name: params.data_name.to_string(),
detail: format!(
"{} {}: expected {} floats, got {}",
MAPPING_BY_VERTICE,
REFERENCE_DIRECT,
params.mapping_offsets.len() * params.components,
channel_data.len()
),
});
}
for i in 0..params.mapping_offsets.len() {
let src = i * params.components;
let istart = params.mapping_offsets[i] as usize;
let iend = istart + params.mapping_counts[i] as usize;
for j in istart..iend {
let dst = params.mappings[j] as usize * params.components;
if src + params.components > channel_data.len()
|| dst + params.components > vertex_out.len()
{
return Err(FbxTryFromReason::InvalidAttributeFormat {
name: params.data_name.to_string(),
detail: format!("length mismatch for {MAPPING_BY_VERTICE}"),
});
}
vertex_out[dst..dst + params.components]
.copy_from_slice(&channel_data[src..src + params.components]);
}
}
} else if by_vertice && is_index_to_direct {
if !has_data || !has_index {
return Ok(Vec::new());
}
let channel_attribute = source.extract_case_insensitive(params.data_name).ok_or(
FbxTryFromReason::InvalidAttributeFormat {
name: params.data_name.to_string(),
detail: "data channel not found".to_string(),
},
)?;
let channel_data_result = parse_f32_array(&channel_attribute);
let Ok(channel_data) = channel_data_result else {
return Err(FbxTryFromReason::InvalidAttributeFormat {
name: params.data_name.to_string(),
detail: format!("invalid float token: {}", channel_data_result.unwrap_err()),
});
};
let channel_index_attribute = source.extract_case_insensitive(params.index_name).ok_or(
FbxTryFromReason::InvalidAttributeFormat {
name: params.index_name.to_string(),
detail: "index channel not found".to_string(),
},
)?;
let channel_index_data_result = parse_i32_array(&channel_index_attribute);
let Ok(channel_index_data) = channel_index_data_result else {
return Err(FbxTryFromReason::InvalidAttributeFormat {
name: params.index_name.to_string(),
detail: format!(
"invalid int token: {}",
channel_index_data_result.unwrap_err()
),
});
};
if channel_index_data.len() != params.mapping_offsets.len() {
return Err(FbxTryFromReason::InvalidAttributeFormat {
name: params.index_name.to_string(),
detail: format!("length mismatch for {MAPPING_BY_VERTICE}"),
});
}
for i in 0..params.mapping_offsets.len() {
let idx = channel_index_data[i] as usize;
let src = idx * params.components; let istart = params.mapping_offsets[i] as usize;
let iend = istart + params.mapping_counts[i] as usize;
for j in istart..iend {
let dst = params.mappings[j] as usize * params.components;
if src + params.components > channel_data.len()
|| dst + params.components > vertex_out.len()
{
return Err(FbxTryFromReason::InvalidAttributeFormat {
name: params.data_name.to_string(),
detail: format!("length mismatch for {MAPPING_BY_VERTICE}"),
});
}
vertex_out[dst..dst + params.components]
.copy_from_slice(&channel_data[src..src + params.components]);
}
}
} else if by_polygon_vertex && is_direct {
if !has_data {
return Ok(Vec::new());
}
let channel_attribute = source.extract_case_insensitive(params.data_name).ok_or(
FbxTryFromReason::InvalidAttributeFormat {
name: params.data_name.to_string(),
detail: "data channel not found".to_string(),
},
)?;
let channel_data_result = parse_f32_array(&channel_attribute);
let Ok(channel_data) = channel_data_result else {
return Err(FbxTryFromReason::InvalidAttributeFormat {
name: params.data_name.to_string(),
detail: format!("invalid float token: {}", channel_data_result.unwrap_err()),
});
};
if channel_data.len() != params.vertex_count * params.components {
return Err(FbxTryFromReason::InvalidAttributeFormat {
name: params.data_name.to_string(),
detail: format!(
"{} {}: expected {} floats, got {}",
MAPPING_BY_POLYGON_VERTEX,
REFERENCE_DIRECT,
params.vertex_count * params.components,
channel_data.len()
),
});
}
vertex_out = channel_data;
} else if by_polygon_vertex && is_index_to_direct {
if !has_data || !has_index {
return Ok(Vec::new());
}
let channel_attribute = source.extract_case_insensitive(params.data_name).ok_or(
FbxTryFromReason::InvalidAttributeFormat {
name: params.data_name.to_string(),
detail: "data channel not found".to_string(),
},
)?;
let channel_data_result = parse_f32_array(&channel_attribute);
let Ok(channel_data) = channel_data_result else {
return Err(FbxTryFromReason::InvalidAttributeFormat {
name: params.data_name.to_string(),
detail: format!("invalid float token: {}", channel_data_result.unwrap_err()),
});
};
let channel_index_attribute = source.extract_case_insensitive(params.index_name).ok_or(
FbxTryFromReason::InvalidAttributeFormat {
name: params.index_name.to_string(),
detail: "index channel not found".to_string(),
},
)?;
let channel_index_data_result = parse_i32_array(&channel_index_attribute);
let Ok(mut channel_index_data) = channel_index_data_result else {
return Err(FbxTryFromReason::InvalidAttributeFormat {
name: params.index_name.to_string(),
detail: format!(
"invalid int token: {}",
channel_index_data_result.unwrap_err()
),
});
};
if channel_index_data.len() > params.vertex_count {
channel_index_data.truncate(params.vertex_count);
}
if channel_index_data.len() != params.vertex_count {
return Err(FbxTryFromReason::InvalidAttributeFormat {
name: params.index_name.to_string(),
detail: format!(
"{} {}: expected {} indices, got {}",
MAPPING_BY_POLYGON_VERTEX,
REFERENCE_INDEX_TO_DIRECT,
params.vertex_count,
channel_index_data.len()
),
});
}
for (slot, &i) in channel_index_data.iter().enumerate() {
let dst = slot * params.components; if dst + params.components > vertex_out.len() {
return Err(FbxTryFromReason::InvalidAttributeFormat {
name: params.data_name.to_string(),
detail: format!("length mismatch for {MAPPING_BY_POLYGON_VERTEX}"),
});
}
if i == -1 {
vertex_out[dst..dst + params.components].fill(0.0);
continue;
}
let src = i as usize * params.components; if src + params.components > channel_data.len() {
return Err(FbxTryFromReason::InvalidAttributeFormat {
name: params.data_name.to_string(),
detail: format!("length mismatch for {MAPPING_BY_POLYGON_VERTEX}"),
});
}
vertex_out[dst..dst + params.components]
.copy_from_slice(&channel_data[src..src + params.components]);
}
} else {
return Ok(Vec::new());
}
Ok(vertex_out)
}
fn read_vertex_data_materials(
source: &HashMap<String, ElementAttribute>,
face_vertex_counts: &[u32],
polygon_vertex_count: usize,
mapping_ty: &str,
reference_ty: &str,
) -> Result<Vec<i32>, FbxTryFromReason> {
let face_count = face_vertex_counts.len();
if face_count == 0 {
return Ok(Vec::new());
}
let Some(mat_el) = source.extract_case_insensitive("Materials") else {
return Ok(Vec::new());
};
let materials_out_result = parse_i32_array(mat_el);
let Ok(materials_out) = materials_out_result else {
return Err(FbxTryFromReason::InvalidAttributeFormat {
name: ATTR_MATERIALS.to_string(),
detail: format!("invalid int token: {}", materials_out_result.unwrap_err()),
});
};
if mapping_ty.eq_ignore_ascii_case(MAPPING_ALL_SAME) {
if materials_out.is_empty() {
return Ok(Vec::new());
}
let count_neg = materials_out.iter().filter(|&&n| n < 0).count();
if count_neg == materials_out.len() {
return Ok(Vec::new());
}
let v = materials_out[0];
Ok(vec![v; polygon_vertex_count])
} else if mapping_ty.eq_ignore_ascii_case(MAPPING_BY_POLYGON)
&& reference_ty.eq_ignore_ascii_case(REFERENCE_INDEX_TO_DIRECT)
{
if materials_out.len() != face_count {
return Err(FbxTryFromReason::InvalidAttributeFormat {
name: ATTR_MATERIALS.to_string(),
detail: format!(
"{}: expected {} material indices, got {}",
MAPPING_BY_POLYGON,
face_count,
materials_out.len()
),
});
}
let count_neg = materials_out.iter().filter(|&&n| n < 0).count();
if count_neg == materials_out.len() {
return Ok(Vec::new());
}
let mut per_corner = Vec::with_capacity(polygon_vertex_count);
for (&m, &n) in materials_out.iter().zip(face_vertex_counts.iter()) {
for _ in 0..n {
per_corner.push(m);
}
}
if per_corner.len() != polygon_vertex_count {
return Err(FbxTryFromReason::InvalidAttributeFormat {
name: ATTR_MATERIALS.to_string(),
detail: format!(
"expanded material indices length {} != polygon vertex count {}",
per_corner.len(),
polygon_vertex_count
),
});
}
Ok(per_corner)
} else {
Ok(Vec::new())
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::convert::TryFrom;
use fbxscii::{
Element, ElementAmphitheatre, ElementAttribute, LeafAttribute, SubTreeAttribute,
};
use crate::OwnedObject;
use super::super::{
FbxTryFromReason, GEOMETRY_LINE_CLASS_NAME, GEOMETRY_MESH_CLASS_NAME, GEOMETRY_TYPE_NAME,
};
use super::MeshGeometry;
fn leaf(tokens: &[&str]) -> ElementAttribute {
ElementAttribute::Leaf(Box::new(LeafAttribute {
key: String::new(),
tokens: tokens.iter().map(|s| (*s).to_string()).collect(),
}))
}
fn append_layer_string_child(
arena: &mut ElementAmphitheatre,
root_idx: usize,
key: &str,
token: &str,
) {
let mut el = Element::new(key.to_string());
el.tokens = vec![token.to_string()];
el.parent_index = Some(root_idx);
let idx = arena.insert(el);
arena.get_mut(root_idx).unwrap().children.push(idx);
}
fn layer_element_normal(mapping: &str, reference: &str, normals_csv: &str) -> ElementAttribute {
let mut arena = ElementAmphitheatre::new();
let root_idx = arena.insert(Element::new("LayerElementNormal".into()));
append_layer_string_child(
&mut arena,
root_idx,
super::ATTR_MAPPING_INFORMATION_TYPE,
mapping,
);
append_layer_string_child(
&mut arena,
root_idx,
super::ATTR_REFERENCE_INFORMATION_TYPE,
reference,
);
let mut normals_el = Element::new("Normals".into());
normals_el.tokens = vec![normals_csv.to_string()];
normals_el.parent_index = Some(root_idx);
let normals_idx = arena.insert(normals_el);
arena.get_mut(root_idx).unwrap().children.push(normals_idx);
ElementAttribute::SubTree(Box::new(SubTreeAttribute {
amphitheatre: arena,
root_element_index: root_idx,
}))
}
fn layer_element_material(
mapping: &str,
reference: &str,
materials_csv: &str,
) -> ElementAttribute {
let mut arena = ElementAmphitheatre::new();
let root_idx = arena.insert(Element::new("LayerElementMaterial".into()));
append_layer_string_child(
&mut arena,
root_idx,
super::ATTR_MAPPING_INFORMATION_TYPE,
mapping,
);
append_layer_string_child(
&mut arena,
root_idx,
super::ATTR_REFERENCE_INFORMATION_TYPE,
reference,
);
let mut mats_el = Element::new(super::ATTR_MATERIALS.into());
mats_el.tokens = vec![materials_csv.to_string()];
mats_el.parent_index = Some(root_idx);
let mats_idx = arena.insert(mats_el);
arena.get_mut(root_idx).unwrap().children.push(mats_idx);
ElementAttribute::SubTree(Box::new(SubTreeAttribute {
amphitheatre: arena,
root_element_index: root_idx,
}))
}
fn owned_mesh(attrs: HashMap<String, ElementAttribute>) -> OwnedObject {
OwnedObject {
object_index: 11,
name: "Geometry::TestMesh".into(),
type_name: GEOMETRY_TYPE_NAME.into(),
class_name: GEOMETRY_MESH_CLASS_NAME.into(),
properties: HashMap::new(),
attributes: attrs,
connected_object_ids: vec![],
object_property_targets: vec![],
pp_property_targets: HashMap::new(),
}
}
fn minimal_base_attrs() -> HashMap<String, ElementAttribute> {
let mut attrs = HashMap::new();
attrs.insert("Vertices".into(), leaf(&["0,0,0,1,0,0,0,1,0"]));
attrs.insert("PolygonVertexIndex".into(), leaf(&["0,1,-3"]));
attrs
}
#[test]
fn minimal_triangle_expands_corners() {
let attrs = minimal_base_attrs();
let mesh = MeshGeometry::try_from(owned_mesh(attrs)).unwrap();
assert_eq!(mesh.vertices.len(), 3);
assert_eq!(mesh.face_vertex_counts, vec![3u32]);
assert!(mesh.normals.is_empty());
assert!(mesh.material_indices.is_empty());
}
#[test]
fn mapping_and_reference_trim_quotes_and_lowercase_keys() {
let mut attrs = minimal_base_attrs();
let mut arena = ElementAmphitheatre::new();
let root_idx = arena.insert(Element::new("LayerElementNormal".into()));
append_layer_string_child(
&mut arena,
root_idx,
"mappinginformationtype",
" \"ByPolygonVertex\" ",
);
append_layer_string_child(&mut arena, root_idx, "referenceinformationtype", "'Direct'");
let mut normals_el = Element::new("Normals".into());
normals_el.tokens = vec!["0,0,1,0,0,1,0,0,1".to_string()];
normals_el.parent_index = Some(root_idx);
let normals_idx = arena.insert(normals_el);
arena.get_mut(root_idx).unwrap().children.push(normals_idx);
attrs.insert(
"LayerElementNormal".into(),
ElementAttribute::SubTree(Box::new(SubTreeAttribute {
amphitheatre: arena,
root_element_index: root_idx,
})),
);
let mesh = MeshGeometry::try_from(owned_mesh(attrs)).unwrap();
assert_eq!(mesh.vertices.len(), 3);
assert_eq!(mesh.normals.len(), 3);
}
#[test]
fn normals_by_polygon_vertex_direct() {
let mut attrs = minimal_base_attrs();
attrs.insert(
"LayerElementNormal".into(),
layer_element_normal("ByPolygonVertex", "Direct", "0,0,1,0,0,1,0,0,1"),
);
let mesh = MeshGeometry::try_from(owned_mesh(attrs)).unwrap();
assert_eq!(
mesh.normals,
vec![[0.0, 0.0, 1.0], [0.0, 0.0, 1.0], [0.0, 0.0, 1.0],]
);
}
#[test]
fn materials_all_same_replicates_first_index() {
let mut attrs = minimal_base_attrs();
attrs.insert(
"LayerElementMaterial".into(),
layer_element_material("AllSame", "IndexToDirect", "5"),
);
let mesh = MeshGeometry::try_from(owned_mesh(attrs)).unwrap();
assert_eq!(mesh.material_indices, vec![5, 5, 5]);
}
#[test]
fn wrong_object_kind_line_geometry() {
let o = OwnedObject {
object_index: 1,
name: "G".into(),
type_name: GEOMETRY_TYPE_NAME.into(),
class_name: GEOMETRY_LINE_CLASS_NAME.into(),
properties: HashMap::new(),
attributes: HashMap::new(),
connected_object_ids: vec![],
object_property_targets: vec![],
pp_property_targets: HashMap::new(),
};
let err = MeshGeometry::try_from(o).unwrap_err();
assert!(matches!(
err.reason,
FbxTryFromReason::WrongObjectKind { ref expected, .. } if expected == "MeshGeometry"
));
}
#[test]
fn missing_mapping_information_type() {
let mut attrs = minimal_base_attrs();
let mut arena = ElementAmphitheatre::new();
let root_idx = arena.insert(Element::new("LayerElementNormal".into()));
append_layer_string_child(
&mut arena,
root_idx,
super::ATTR_REFERENCE_INFORMATION_TYPE,
"Direct",
);
let mut normals_el = Element::new("Normals".into());
normals_el.tokens = vec!["0,0,1,0,0,1,0,0,1".to_string()];
normals_el.parent_index = Some(root_idx);
let normals_idx = arena.insert(normals_el);
arena.get_mut(root_idx).unwrap().children.push(normals_idx);
attrs.insert(
"LayerElementNormal".into(),
ElementAttribute::SubTree(Box::new(SubTreeAttribute {
amphitheatre: arena,
root_element_index: root_idx,
})),
);
let err = MeshGeometry::try_from(owned_mesh(attrs)).unwrap_err();
assert!(matches!(
err.reason,
FbxTryFromReason::MissingAttribute { ref name } if name == "MappingInformationType"
));
}
#[test]
fn missing_reference_information_type() {
let mut attrs = minimal_base_attrs();
let mut arena = ElementAmphitheatre::new();
let root_idx = arena.insert(Element::new("LayerElementNormal".into()));
append_layer_string_child(
&mut arena,
root_idx,
super::ATTR_MAPPING_INFORMATION_TYPE,
"ByPolygonVertex",
);
let mut normals_el = Element::new("Normals".into());
normals_el.tokens = vec!["0,0,1,0,0,1,0,0,1".to_string()];
normals_el.parent_index = Some(root_idx);
let normals_idx = arena.insert(normals_el);
arena.get_mut(root_idx).unwrap().children.push(normals_idx);
attrs.insert(
"LayerElementNormal".into(),
ElementAttribute::SubTree(Box::new(SubTreeAttribute {
amphitheatre: arena,
root_element_index: root_idx,
})),
);
let err = MeshGeometry::try_from(owned_mesh(attrs)).unwrap_err();
assert!(matches!(
err.reason,
FbxTryFromReason::MissingAttribute { ref name } if name == "ReferenceInformationType"
));
}
}