#![warn(missing_docs)]
use blend::{Blend, runtime::Instance};
use bevy_app::prelude::*;
use bevy_asset::{AddAsset, AssetLoader, LoadContext, LoadedAsset};
use bevy_render::{
mesh::{Indices, Mesh},
pipeline::PrimitiveTopology,
};
use bevy_utils::BoxedFuture;
#[macro_export]
macro_rules! blender_mesh {
($blend_file:literal, $mesh_name:literal) => {
format!("{}#ME{}", $blend_file, $mesh_name).as_str()
}
}
pub struct BlenderPlugin;
impl Plugin for BlenderPlugin {
fn build(&self, app: &mut AppBuilder) {
app.init_asset_loader::<BlenderLoader>();
}
}
#[derive(thiserror::Error, Debug)]
pub enum BevyBlenderError {
#[error("Invalid .blend file {file_name:?}, missing magic number")]
InvalidBlendFile {
file_name: String,
},
#[error("Invalid instance type (expected {expected:?}, got {found:?})")]
InvalidInstanceType {
expected: String,
found: String,
}
}
#[derive(Default)]
struct BlenderLoader;
impl AssetLoader for BlenderLoader {
fn load<'a>(
&'a self,
bytes: &'a [u8],
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, anyhow::Result<()>> {
Box::pin(async move { Ok(load_blend_assets(bytes, load_context).await?) })
}
fn extensions(&self) -> &[&str] {
static EXTENSIONS: &[&str] = &["blend"];
EXTENSIONS
}
}
async fn load_blend_assets <'a, 'b>(
bytes: &'a [u8],
load_context: &'a mut LoadContext<'b>,
) -> anyhow::Result<()> {
if bytes[0..7] != [0x42, 0x4c, 0x45, 0x4e, 0x44, 0x45, 0x52] {
return Err(anyhow::Error::new(
BevyBlenderError::InvalidBlendFile{
file_name: String::from(load_context.path().to_str().unwrap()),
}));
}
let blend = Blend::new(bytes);
for mesh in blend.get_by_code(*b"ME") {
let label = mesh.get("id").get_string("name");
if !label.starts_with("ME_") {
load_context.set_labeled_asset(label.as_str(), LoadedAsset::new(instance_to_mesh(mesh)?));
}
}
Ok(())
}
fn instance_to_mesh(instance: Instance) -> 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 face in blender_faces {
let start = face.get_i32("loopstart");
let end = start + 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);
}
match faceloop.len() {
3 => {
indices.push(faceloop[1]); indices.push(faceloop[0]);
indices.push(faceloop[2]); indices.push(faceloop[1]);
indices.push(faceloop[0]); indices.push(faceloop[2]);
},
4 => {
indices.push(faceloop[1]); indices.push(faceloop[0]);
indices.push(faceloop[2]); indices.push(faceloop[1]);
indices.push(faceloop[0]); indices.push(faceloop[2]);
indices.push(faceloop[2]); indices.push(faceloop[0]);
indices.push(faceloop[3]); indices.push(faceloop[2]);
indices.push(faceloop[0]); indices.push(faceloop[3]);
},
_ => {
eprintln!("Warning: bevy_blend trying to create {}-gon on mesh {}. This is not currently supported.", faceloop.len(), instance.get("id").get_string("name"));
}
}
}
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]]);
let n = no_to_f32(vert.get_i16_vec("no"));
normals.push([n[0], n[2], n[1]]);
}
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.set_attribute(Mesh::ATTRIBUTE_POSITION, positions);
mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
Ok(mesh)
}