use bevy_math::Vec3;
use bevy_render::{
mesh::{Indices, Mesh},
render_resource::PrimitiveTopology,
};
use blend::runtime::Instance;
use crate::BevyBlenderError;
#[macro_export]
macro_rules! blender_mesh {
($blend_file:literal, $mesh_name:literal) => {
format!("{}#ME{}", $blend_file, $mesh_name).as_str()
};
}
pub(crate) fn instance_to_mesh(
instance: Instance,
blend_version: (u8, u8, u8),
) -> anyhow::Result<Mesh> {
if instance.type_name != "Mesh" {
return Err(anyhow::Error::new(BevyBlenderError::InvalidInstanceType {
expected: String::from("Mesh"),
found: instance.type_name,
}));
}
fn no_to_f32(no: Vec<i16>) -> Vec<f32> {
let mut v = Vec::new();
for i in no {
v.push((i as f32) / (i16::MAX as f32));
}
v
}
let blender_faces = instance.get_iter("mpoly").collect::<Vec<_>>();
let blender_loops = instance.get_iter("mloop").collect::<Vec<_>>();
let blender_uvs = instance.get_iter("mloopuv").collect::<Vec<_>>();
let blender_verts = instance.get_iter("mvert").collect::<Vec<_>>();
let mut indices: Vec<u32> = Vec::new();
for blender_face in &blender_faces {
let start = blender_face.get_i32("loopstart");
let end = start + blender_face.get_i32("totloop");
let mut faceloop: Vec<u32> = Vec::new();
for i in start..end {
faceloop.push(blender_loops[i as usize].get_i32("v") as u32);
}
let mut faces: Vec<Vec<u32>> = Vec::new();
let mut i = 0;
while faceloop.len() > 3 {
if i >= faceloop.len() {
i = 0;
}
let mut face = Vec::new();
face.push(faceloop[i]);
i += 1;
if i >= faceloop.len() {
i = 0;
}
face.push(faceloop[i]);
let mut j = i + 1;
if j >= faceloop.len() {
j = 0;
}
face.push(faceloop[j]);
faces.push(face);
faceloop.remove(i);
}
faces.push(faceloop);
for face in faces {
indices.push(face[2]);
indices.push(face[0]);
indices.push(face[1]);
indices.push(face[2]);
indices.push(face[0]);
indices.push(face[1]);
}
}
let mut positions: Vec<[f32; 3]> = Vec::new();
let mut normals: Vec<[f32; 3]> = Vec::new();
let mut uvs: Vec<[f32; 2]> = vec![[0.0, 0.0]; blender_verts.len()];
for vert in &blender_verts {
let p = vert.get_f32_vec("co");
positions.push([p[0], p[2], -p[1]]);
}
match blend_version {
(0..=2, _, _) => {
for vert in &blender_verts {
let n = no_to_f32(vert.get_i16_vec("no"));
normals.push([n[0], n[2], -n[1]]);
}
}
(3.., _, _) => {
normals = calculate_vertex_normals(&blender_faces, &blender_loops, &positions);
}
}
for i in 0..blender_uvs.len() {
let uv = blender_uvs[i].get_f32_vec("uv");
uvs[blender_loops[i].get_i32("v") as usize] = [uv[0], uv[1]];
}
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
mesh.set_indices(Some(Indices::U32(indices)));
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
Ok(mesh)
}
fn calculate_vertex_normals(
blender_faces: &Vec<Instance>,
blender_loops: &Vec<Instance>,
positions: &Vec<[f32; 3]>,
) -> Vec<[f32; 3]> {
let mut normals: Vec<Vec3> = Vec::new();
for blender_face in blender_faces {
let start = blender_face.get_i32("loopstart");
let end = start + blender_face.get_i32("totloop");
let mut faceloop: Vec<u32> = Vec::new();
for i in start..end {
faceloop.push(blender_loops[i as usize].get_i32("v") as u32);
}
let v1 = positions[faceloop[0] as usize];
let v2 = positions[faceloop[1] as usize];
let v3 = positions[faceloop[faceloop.len() - 1] as usize];
let e1 = Vec3::new(v2[0] - v1[0], v2[1] - v1[1], v2[2] - v1[2]);
let e2 = Vec3::new(v1[0] - v3[0], v1[1] - v3[1], v1[2] - v3[2]);
let n = e2.cross(e1).normalize();
for vertex in faceloop {
while vertex >= normals.len() as u32 {
normals.push(Vec3::new(0.0, 0.0, 0.0));
}
normals[vertex as usize] += n;
}
}
let mut normals_out: Vec<[f32; 3]> = Vec::new();
for normal in normals {
normals_out.push(normal.normalize().into());
}
return normals_out;
}
#[cfg(nightly)]
mod tests {
use super::instance_to_mesh;
use blend::{Blend, Instance};
extern crate test;
use test::Bencher;
fn get_mesh_by_name<'a>(blend: &'a Blend, name: &str) -> Option<Instance<'a>> {
for mesh in blend.get_by_code(*b"ME") {
if mesh.get("id").get_string("name") == name {
return Some(mesh);
}
}
None
}
#[bench]
fn instance_to_mesh_cube_192(b: &mut Bencher) {
let blend = Blend::from_path(
std::env::current_dir()
.unwrap()
.join(std::path::PathBuf::from("assets").join("benches31.blend")),
);
let version_raw = blend.blend.header.version;
let version = (
version_raw[0] - 48,
version_raw[1] - 48,
version_raw[2] - 48,
);
let mesh_instance = get_mesh_by_name(&blend, "MECube_192").unwrap();
b.iter(|| {
instance_to_mesh(mesh_instance.clone(), version).unwrap();
});
}
#[bench]
fn instance_to_mesh_cube_768(b: &mut Bencher) {
let blend = Blend::from_path(
std::env::current_dir()
.unwrap()
.join(std::path::PathBuf::from("assets").join("benches31.blend")),
);
let version_raw = blend.blend.header.version;
let version = (
version_raw[0] - 48,
version_raw[1] - 48,
version_raw[2] - 48,
);
let mesh_instance = get_mesh_by_name(&blend, "MECube_768").unwrap();
b.iter(|| {
instance_to_mesh(mesh_instance.clone(), version).unwrap();
});
}
#[bench]
fn instance_to_mesh_cube_3072(b: &mut Bencher) {
let blend = Blend::from_path(
std::env::current_dir()
.unwrap()
.join(std::path::PathBuf::from("assets").join("benches31.blend")),
);
let version_raw = blend.blend.header.version;
let version = (
version_raw[0] - 48,
version_raw[1] - 48,
version_raw[2] - 48,
);
let mesh_instance = get_mesh_by_name(&blend, "MECube_3072").unwrap();
b.iter(|| {
instance_to_mesh(mesh_instance.clone(), version).unwrap();
});
}
#[bench]
fn instance_to_mesh_cube_12288(b: &mut Bencher) {
let blend = Blend::from_path(
std::env::current_dir()
.unwrap()
.join(std::path::PathBuf::from("assets").join("benches31.blend")),
);
let version_raw = blend.blend.header.version;
let version = (
version_raw[0] - 48,
version_raw[1] - 48,
version_raw[2] - 48,
);
let mesh_instance = get_mesh_by_name(&blend, "MECube_12288").unwrap();
b.iter(|| {
instance_to_mesh(mesh_instance.clone(), version).unwrap();
});
}
#[bench]
fn instance_to_mesh_cube_49125(b: &mut Bencher) {
let blend = Blend::from_path(
std::env::current_dir()
.unwrap()
.join(std::path::PathBuf::from("assets").join("benches31.blend")),
);
let version_raw = blend.blend.header.version;
let version = (
version_raw[0] - 48,
version_raw[1] - 48,
version_raw[2] - 48,
);
let mesh_instance = get_mesh_by_name(&blend, "MECube_49125").unwrap();
b.iter(|| {
instance_to_mesh(mesh_instance.clone(), version).unwrap();
});
}
}