use std::collections::HashMap;
use draco_core::draco_types::DataType;
use draco_core::geometry_attribute::{GeometryAttributeType, PointAttribute};
use draco_core::geometry_indices::{FaceIndex, PointIndex};
use draco_core::mesh::Mesh;
use crate::fbx_scene::{
FbxBinormalSet, FbxColorSet, FbxCreaseLayer, FbxLayerSet, FbxMeshInstance, FbxMeshLayers,
FbxNormalSet, FbxSmoothingLayer, FbxTangentSet, FbxUvSet,
};
#[derive(Debug, Clone, Default, PartialEq)]
pub struct FbxRenderLayer<T> {
pub name: Option<String>,
pub values: Vec<T>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct FbxRenderMesh {
pub positions: Vec<[f32; 3]>,
pub normals: Vec<FbxRenderLayer<[f32; 3]>>,
pub uvs: Vec<FbxRenderLayer<[f32; 2]>>,
pub colors: Vec<FbxRenderLayer<[f32; 4]>>,
pub tangents: Vec<FbxRenderLayer<[f32; 4]>>,
pub binormals: Vec<FbxRenderLayer<[f32; 4]>>,
pub indices: Vec<u32>,
pub polygon_sizes: Vec<u32>,
pub corner_to_control_point: Vec<u32>,
pub corner_to_polygon: Vec<u32>,
}
impl FbxRenderMesh {
pub fn corner_count(&self) -> usize {
self.positions.len()
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct FbxGeometryLayers<'a> {
pub control_points: &'a [[f32; 3]],
pub polygon_vertex_indices: &'a [i32],
pub uv_sets: &'a [FbxUvSet],
pub normal_sets: &'a [FbxNormalSet],
pub color_sets: &'a [FbxColorSet],
pub tangent_sets: &'a [FbxTangentSet],
pub binormal_sets: &'a [FbxBinormalSet],
pub smoothing_layers: &'a [FbxSmoothingLayer],
pub crease_layers: &'a [FbxCreaseLayer],
}
impl<'a> FbxGeometryLayers<'a> {
pub fn from_instance(instance: &'a FbxMeshInstance) -> Self {
Self::new(
&instance.control_points,
&instance.polygon_vertex_indices,
&instance.layers,
)
}
pub fn new(
control_points: &'a [[f32; 3]],
polygon_vertex_indices: &'a [i32],
layers: &'a FbxMeshLayers,
) -> Self {
Self {
control_points,
polygon_vertex_indices,
uv_sets: &layers.uv_sets,
normal_sets: &layers.normal_sets,
color_sets: &layers.color_sets,
tangent_sets: &layers.tangent_sets,
binormal_sets: &layers.binormal_sets,
smoothing_layers: &layers.smoothing_layers,
crease_layers: &layers.crease_layers,
}
}
}
#[derive(Debug, Clone, Copy)]
struct EmittedCorner {
control_point: u32,
source_corner: usize,
polygon: u32,
}
fn resolve_layer_value<const N: usize>(
mapping: Option<&str>,
reference: Option<&str>,
indices: &[i32],
values: &[[f32; N]],
corner: EmittedCorner,
) -> [f32; N] {
let logical = match mapping {
Some("ByPolygonVertex") => corner.source_corner,
Some("ByPolygon") => corner.polygon as usize,
Some("AllSame") | Some("AllSameOrPolygon") => 0,
_ => corner.control_point as usize,
};
let value_index = if reference == Some("IndexToDirect") {
match indices.get(logical).copied() {
Some(index) if index < 0 => return [0.0; N],
Some(index) => index as usize,
None => logical,
}
} else {
logical
};
values.get(value_index).copied().unwrap_or([0.0; N])
}
fn resolve_layer<const N: usize>(
set: &FbxLayerSet<N>,
corners: &[EmittedCorner],
) -> FbxRenderLayer<[f32; N]> {
FbxRenderLayer {
name: set.name.clone(),
values: corners
.iter()
.map(|&corner| {
resolve_layer_value(
set.mapping.as_deref(),
set.reference.as_deref(),
&set.indices,
&set.values,
corner,
)
})
.collect(),
}
}
pub fn expand_to_render_mesh(source: FbxGeometryLayers<'_>) -> FbxRenderMesh {
let FbxGeometryLayers {
control_points,
polygon_vertex_indices,
uv_sets,
normal_sets,
color_sets,
tangent_sets,
binormal_sets,
smoothing_layers: _,
crease_layers: _,
} = source;
let mut render = FbxRenderMesh::default();
if control_points.is_empty() || polygon_vertex_indices.is_empty() {
return render;
}
let mut emitted: Vec<EmittedCorner> = Vec::new();
let mut polygon: Vec<EmittedCorner> = Vec::new();
let mut polygon_index = 0u32;
for (corner, encoded) in polygon_vertex_indices.iter().enumerate() {
let control_point = if *encoded < 0 {
!*encoded as u32
} else {
*encoded as u32
};
polygon.push(EmittedCorner {
control_point,
source_corner: corner,
polygon: polygon_index,
});
if *encoded < 0 {
render.polygon_sizes.push(polygon.len() as u32);
for offset in 1..polygon.len().saturating_sub(1) {
for &vertex in &[polygon[0], polygon[offset], polygon[offset + 1]] {
emitted.push(vertex);
render.corner_to_polygon.push(polygon_index);
}
}
polygon.clear();
polygon_index += 1;
}
}
render.positions = emitted
.iter()
.map(|corner| {
control_points
.get(corner.control_point as usize)
.copied()
.unwrap_or([0.0; 3])
})
.collect();
render.corner_to_control_point = emitted.iter().map(|c| c.control_point).collect();
render.indices = (0..emitted.len() as u32).collect();
render.uvs = uv_sets.iter().map(|s| resolve_layer(s, &emitted)).collect();
render.normals = normal_sets
.iter()
.map(|s| resolve_layer(s, &emitted))
.collect();
render.colors = color_sets
.iter()
.map(|s| resolve_layer(s, &emitted))
.collect();
render.tangents = tangent_sets
.iter()
.map(|s| resolve_layer(&s.layer, &emitted))
.collect();
render.binormals = binormal_sets
.iter()
.map(|s| resolve_layer(&s.layer, &emitted))
.collect();
render
}
pub fn build_draco_mesh(render: &FbxRenderMesh) -> Mesh {
let normals = render.normals.first();
let uvs = render.uvs.first();
let colors = render.colors.first();
type WeldKey = (
[u32; 3],
Option<[u32; 3]>,
Option<[u32; 2]>,
Option<[u32; 4]>,
);
let mut unique: HashMap<WeldKey, u32> = HashMap::new();
let mut order: Vec<usize> = Vec::new();
let mut remapped: Vec<u32> = Vec::with_capacity(render.corner_count());
for corner in 0..render.corner_count() {
let position = render.positions[corner].map(f32::to_bits);
let normal = normals.map(|layer| layer.values[corner].map(f32::to_bits));
let uv = uvs.map(|layer| layer.values[corner].map(f32::to_bits));
let color = colors.map(|layer| layer.values[corner].map(f32::to_bits));
let key: WeldKey = (position, normal, uv, color);
let next = unique.len() as u32;
let index = *unique.entry(key).or_insert_with(|| {
order.push(corner);
next
});
remapped.push(index);
}
let point_count = order.len();
let mut mesh = Mesh::new();
mesh.set_num_points(point_count);
let mut position = PointAttribute::new();
position.init(
GeometryAttributeType::Position,
3,
DataType::Float32,
false,
point_count,
);
for (index, &corner) in order.iter().enumerate() {
let bytes: Vec<u8> = render.positions[corner]
.iter()
.flat_map(|value| value.to_le_bytes())
.collect();
position.buffer_mut().write(index * 12, &bytes);
}
mesh.add_attribute(position);
if let Some(layer) = normals {
let mut normal = PointAttribute::new();
normal.init(
GeometryAttributeType::Normal,
3,
DataType::Float32,
false,
point_count,
);
for (index, &corner) in order.iter().enumerate() {
let bytes: Vec<u8> = layer.values[corner]
.iter()
.flat_map(|value| value.to_le_bytes())
.collect();
normal.buffer_mut().write(index * 12, &bytes);
}
mesh.add_attribute(normal);
}
if let Some(layer) = uvs {
let mut tex_coord = PointAttribute::new();
tex_coord.init(
GeometryAttributeType::TexCoord,
2,
DataType::Float32,
false,
point_count,
);
for (index, &corner) in order.iter().enumerate() {
let bytes: Vec<u8> = layer.values[corner]
.iter()
.flat_map(|value| value.to_le_bytes())
.collect();
tex_coord.buffer_mut().write(index * 8, &bytes);
}
mesh.add_attribute(tex_coord);
}
if let Some(layer) = colors {
let mut color = PointAttribute::new();
color.init(
GeometryAttributeType::Color,
4,
DataType::Float32,
false,
point_count,
);
for (index, &corner) in order.iter().enumerate() {
let bytes: Vec<u8> = layer.values[corner]
.iter()
.flat_map(|value| value.to_le_bytes())
.collect();
color.buffer_mut().write(index * 16, &bytes);
}
mesh.add_attribute(color);
}
mesh.set_num_faces(remapped.len() / 3);
for (face, triangle) in remapped.chunks_exact(3).enumerate() {
mesh.set_face(
FaceIndex(face as u32),
[
PointIndex(triangle[0]),
PointIndex(triangle[1]),
PointIndex(triangle[2]),
],
);
}
mesh
}
impl FbxMeshInstance {
pub fn to_render_mesh(&self) -> FbxRenderMesh {
expand_to_render_mesh(FbxGeometryLayers::from_instance(self))
}
pub fn to_draco_mesh(&self) -> Mesh {
let render = self.to_render_mesh();
if render.positions.is_empty() {
return self.mesh.clone();
}
build_draco_mesh(&render)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fbx_scene::FbxMeshInstance;
fn seamed_quad() -> FbxMeshInstance {
FbxMeshInstance {
control_points: vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
],
polygon_vertex_indices: vec![0, 1, 2, !3],
layers: FbxMeshLayers {
uv_sets: vec![FbxUvSet {
name: Some("map1".to_string()),
mapping: Some("ByPolygonVertex".to_string()),
reference: Some("Direct".to_string()),
values: vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.5, 0.5]],
indices: Vec::new(),
}],
..Default::default()
},
..Default::default()
}
}
#[test]
fn a_quad_fans_into_two_triangles_over_four_corners() {
let render = seamed_quad().to_render_mesh();
assert_eq!(render.polygon_sizes, vec![4]);
assert_eq!(render.corner_count(), 6, "two fan triangles");
assert_eq!(render.indices, vec![0, 1, 2, 3, 4, 5]);
assert_eq!(render.corner_to_control_point, vec![0, 1, 2, 0, 2, 3]);
assert!(render.corner_to_polygon.iter().all(|&p| p == 0));
}
#[test]
fn per_corner_uvs_follow_the_corner_not_the_control_point() {
let render = seamed_quad().to_render_mesh();
let uv = &render.uvs[0].values;
assert_eq!(uv[0], [0.0, 0.0]);
assert_eq!(uv[1], [1.0, 0.0]);
assert_eq!(uv[2], [1.0, 1.0]);
assert_eq!(render.uvs[0].name.as_deref(), Some("map1"));
}
#[test]
fn welding_keeps_seams_but_drops_exact_duplicates() {
let mesh = seamed_quad().to_draco_mesh();
assert_eq!(mesh.num_points(), 4);
assert_eq!(mesh.num_faces(), 2);
}
fn probe_corner() -> EmittedCorner {
EmittedCorner {
control_point: 1,
source_corner: 5,
polygon: 2,
}
}
#[test]
fn a_negative_index_to_direct_entry_yields_a_default_rather_than_panicking() {
let value: [f32; 2] = resolve_layer_value(
Some("ByPolygonVertex"),
Some("IndexToDirect"),
&[-31],
&[[1.0, 2.0]],
EmittedCorner {
control_point: 0,
source_corner: 0,
polygon: 0,
},
);
assert_eq!(value, [0.0, 0.0]);
}
#[test]
fn each_mapping_addresses_its_own_domain() {
let values: Vec<[f32; 1]> = (0..8).map(|i| [i as f32]).collect();
let resolve = |mapping| {
resolve_layer_value(Some(mapping), Some("Direct"), &[], &values, probe_corner())
};
assert_eq!(resolve("ByPolygonVertex"), [5.0]);
assert_eq!(resolve("ByVertice"), [1.0]);
assert_eq!(resolve("AllSame"), [0.0]);
assert_eq!(resolve("ByPolygon"), [2.0]);
}
#[test]
fn an_instance_without_raw_geometry_expands_to_nothing() {
let mut instance = seamed_quad();
instance.control_points.clear();
instance.polygon_vertex_indices.clear();
assert_eq!(instance.to_render_mesh(), FbxRenderMesh::default());
}
}