engine/gltf-loader/src/primitive.rs
2024-08-23 13:22:09 +02:00

279 lines
8.4 KiB
Rust

use super::skin::Skin;
use utilities::prelude::cgmath::{Matrix4, Vector2, Vector3, Vector4};
use gltf;
use gltf::{
buffer::Buffer,
material::{AlphaMode, NormalTexture},
mesh::{util::ReadTexCoords, BoundingBox, Reader},
texture::Info,
};
#[derive(Debug)]
pub struct TextureInfo {
pub image_index: usize,
pub texture_index: u32,
pub texture_coordinates: Vec<Vector2<f32>>,
}
#[derive(Debug, Copy, Clone)]
pub struct Material {
pub color: [f32; 4],
pub metallic_factor: f32,
pub emissive_factor: [f32; 3],
pub roughness_factor: f32,
pub alpha_mode: AlphaMode,
pub alpha_cut_off: f32,
}
impl Default for Material {
fn default() -> Self {
Self {
color: [1.0, 1.0, 1.0, 1.0],
metallic_factor: 1.0,
emissive_factor: [0.0, 0.0, 0.0],
roughness_factor: 1.0,
alpha_mode: AlphaMode::Blend,
alpha_cut_off: 1.0,
}
}
}
#[derive(Debug)]
pub struct Primitive {
pub positions: Option<Vec<Vector3<f32>>>,
pub normals: Option<Vec<Vector3<f32>>>,
pub tangents: Option<Vec<Vector4<f32>>>,
pub indices: Option<Vec<u32>>,
pub bounding_box: BoundingBox,
pub color_texture_info: Option<TextureInfo>,
pub normal_texture_info: Option<TextureInfo>,
pub emissive_texture_info: Option<TextureInfo>,
pub roughness_texture_info: Option<TextureInfo>,
pub joint_indices: Option<Vec<[i32; 4]>>,
pub weights: Option<Vec<[f32; 4]>>,
pub material: Material,
}
impl Primitive {
pub fn new(
gltf_primitive: gltf::Primitive<'_>,
buffers: &Vec<gltf::buffer::Data>,
skin: Option<&Skin>,
) -> Primitive {
// create reader
let reader = gltf_primitive.reader(|buffer| Some(&buffers[buffer.index()]));
// read positions
let positions = match reader.read_positions() {
Some(iter) => Some(iter.map(|vertex| Vector3::from(vertex)).collect()),
None => None,
};
// read normals
let normals = match reader.read_normals() {
Some(iter) => Some(iter.map(|normal| Vector3::from(normal)).collect()),
None => None,
};
// read tangents
let tangents = match reader.read_tangents() {
Some(iter) => Some(iter.map(|tangent| Vector4::from(tangent)).collect()),
None => None,
};
// read indices
let indices = match reader.read_indices() {
Some(iter) => Some(iter.into_u32().map(|index| index).collect()),
None => None,
};
let material = gltf_primitive.material();
let normal_texture_info =
Self::load_normal_texture_info(material.normal_texture(), &reader);
let metallic_roughness = material.pbr_metallic_roughness();
let color_texture_info =
Self::load_info_texture_info(metallic_roughness.base_color_texture(), &reader);
let emissive_texture_info =
Self::load_info_texture_info(material.emissive_texture(), &reader);
let roughness_texture_info =
Self::load_info_texture_info(metallic_roughness.metallic_roughness_texture(), &reader);
let material = Material {
color: metallic_roughness.base_color_factor(),
metallic_factor: metallic_roughness.metallic_factor(),
emissive_factor: material.emissive_factor(),
roughness_factor: metallic_roughness.roughness_factor(),
alpha_mode: material.alpha_mode(),
alpha_cut_off: material.alpha_cutoff().unwrap_or(1.0),
};
let (joint_indices, weights): (Option<Vec<[i32; 4]>>, Option<Vec<[f32; 4]>>) = match skin {
Some(skin) => {
let joint_ids = skin.joints();
let joint_indices = match reader.read_joints(skin.index as u32) {
Some(iter) => Some(
iter.into_u16()
.map(|joint| {
let mut joints = [0, 0, 0, 0];
for j in 0..4 {
joints[j] = joint_ids[joint[j] as usize] as i32;
}
joints
})
.collect(),
),
None => None,
};
let weights = match reader.read_weights(skin.index as u32) {
Some(iter) => Some(
iter.into_f32()
.map(|weight| {
let mut weights = [0.0, 0.0, 0.0, 0.0];
let mut sum = 0.0;
for w in &weight {
sum += w;
}
let inv_sum = 1.0 / sum;
for (j, w) in weight.iter().enumerate() {
weights[j] = w * inv_sum;
}
weights
})
.collect(),
),
None => None,
};
(joint_indices, weights)
}
None => (None, None),
};
if let Some(ref joint_indices) = joint_indices {
if let Some(ref weights) = weights {
debug_assert!(joint_indices.len() == weights.len());
}
}
Primitive {
positions,
normals,
tangents,
indices,
bounding_box: gltf_primitive.bounding_box(),
color_texture_info,
normal_texture_info,
emissive_texture_info,
roughness_texture_info,
joint_indices,
weights,
material,
}
}
pub(crate) fn scale_bb(&mut self, m: Matrix4<f32>) {
self.bounding_box = BoundingBox {
min: Self::mul(self.bounding_box.min, m),
max: Self::mul(self.bounding_box.max, m),
};
}
fn mul(v: impl Into<Vector3<f32>>, m: Matrix4<f32>) -> [f32; 3] {
let t = m * v.into().extend(1.0);
[t.x / t.w, t.y / t.w, t.z / t.w]
}
#[inline]
fn load_info_texture_info<'a, 's, F>(
info: Option<Info<'_>>,
reader: &Reader<'a, 's, F>,
) -> Option<TextureInfo>
where
F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>,
{
info.map(|info| {
Self::load_texture_info(
reader.read_tex_coords(info.tex_coord()),
info.texture().source().index(),
info.tex_coord(),
)
})
.flatten()
}
#[inline]
fn load_normal_texture_info<'a, 's, F>(
info: Option<NormalTexture<'_>>,
reader: &Reader<'a, 's, F>,
) -> Option<TextureInfo>
where
F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>,
{
info.map(|info| {
Self::load_texture_info(
reader.read_tex_coords(info.tex_coord()),
info.texture().source().index(),
info.tex_coord(),
)
})
.flatten()
}
#[inline]
fn load_texture_info<'s>(
read: Option<ReadTexCoords<'s>>,
image_index: usize,
texture_index: u32,
) -> Option<TextureInfo> {
match read {
Some(iter) => {
let texture_coordinates = iter
.into_f32()
.map(|texture_coordinate| Vector2::from(texture_coordinate))
.collect();
Some(TextureInfo {
image_index,
texture_index,
texture_coordinates,
})
}
None => None,
}
}
}
impl PartialEq for Primitive {
fn eq(&self, other: &Primitive) -> bool {
self.positions.is_some() == other.positions.is_some()
&& self.normals.is_some() == other.normals.is_some()
&& self.indices.is_some() == other.indices.is_some()
&& self.color_texture_info.is_some() == other.color_texture_info.is_some()
&& self.normal_texture_info.is_some() == other.normal_texture_info.is_some()
&& self.joint_indices.is_some() == other.joint_indices.is_some()
&& self.weights.is_some() == other.weights.is_some()
}
}