use crate::{right_hand_zup_to_right_hand_yup, BevyBlenderError};
use bevy_asset::{AssetServer, Handle};
use bevy_ecs::{
bundle::Bundle,
system::{Commands, ResMut},
};
use bevy_hierarchy::{BuildChildren, ChildBuilder};
use bevy_log::error;
use bevy_math::{Mat4, Vec4};
use bevy_pbr::prelude::StandardMaterial;
use bevy_render::{
mesh::Mesh,
prelude::{ComputedVisibility, Visibility},
};
use bevy_transform::prelude::{GlobalTransform, Transform};
use blend::{Blend, Instance};
#[derive(Bundle)]
pub struct BlenderObjectBundle {
pub mesh: Handle<Mesh>,
pub material: Handle<StandardMaterial>,
pub visibility: Visibility,
pub computed_visibility: ComputedVisibility,
pub transform: Transform,
pub global_transform: GlobalTransform,
}
impl Default for BlenderObjectBundle {
fn default() -> Self {
Self {
mesh: Default::default(),
visibility: Default::default(),
computed_visibility: Default::default(),
material: Default::default(),
transform: Default::default(),
global_transform: Default::default(),
}
}
}
impl BlenderObjectBundle {
pub fn new(
asset_server: &ResMut<AssetServer>,
blender_file: &str,
object_name: &str,
) -> anyhow::Result<Self> {
let blend = Blend::from_path(
std::env::current_dir()
.unwrap()
.join(std::path::PathBuf::from("assets").join(blender_file)),
);
Self::new_from_blend(asset_server, &blend, blender_file, object_name)
}
pub fn new_from_blend(
asset_server: &ResMut<AssetServer>,
blend: &Blend,
blender_file: &str,
object_name: &str,
) -> anyhow::Result<Self> {
let obj = match get_object_by_name(blend, format!("OB{}", object_name).as_str()) {
Some(o) => o,
None => {
return Err(anyhow::Error::new(BevyBlenderError::MissingAsset {
asset_name: object_name.into(),
blend_file: blender_file.into(),
}));
}
};
let mut materials = obj.get("data").get_iter("mat");
let material: Handle<StandardMaterial> = match materials.next() {
None => Handle::default(),
Some(material) => {
if (material.get_char("use_nodes") as u8) == 0 {
asset_server.load(
format!("{}#{}", blender_file, material.get("id").get_string("name"))
.as_str(),
)
} else {
Handle::default()
}
}
};
let world_matrix = get_world_matrix(&obj);
let corrected_matrix = right_hand_zup_to_right_hand_yup(&world_matrix);
let transform = Transform::from_matrix(corrected_matrix);
return Ok(Self {
mesh: asset_server.load(
format!(
"{}#{}",
blender_file,
obj.get("data").get("id").get_string("name")
)
.as_str(),
),
material,
transform,
..Default::default()
});
}
}
fn get_object_by_name<'a>(blend: &'a Blend, name: &str) -> Option<Instance<'a>> {
for obj in blend.get_by_code(*b"OB") {
if obj.get("id").get_string("name") == name {
return Some(obj);
}
}
None
}
fn get_children<'a>(blend: &'a Blend, name: &str) -> Vec<Instance<'a>> {
let mut children: Vec<Instance<'a>> = Vec::new();
for obj in blend.get_by_code(*b"OB") {
if obj.is_valid("parent") && obj.get("parent").get("id").get_string("name") == name {
children.push(obj);
}
}
children
}
fn get_world_matrix(object: &Instance) -> Mat4 {
let w = object.get_f32_vec("obmat");
Mat4::from_cols(
Vec4::from_slice(&w[0..4]), Vec4::from_slice(&w[4..8]), Vec4::from_slice(&w[8..12]), Vec4::from_slice(&w[12..16]), )
}
pub fn spawn_blender_object(
commands: &mut Commands,
asset_server: &ResMut<AssetServer>,
blender_file: &str,
root_object_name: &str,
spawn_children: bool,
parent_transform: Option<Transform>,
) {
match spawn_blender_object_with_error(
commands,
asset_server,
blender_file,
root_object_name,
spawn_children,
parent_transform,
) {
Ok(_) => {}
Err(e) => {
error!("{}", e);
}
}
}
fn spawn_blender_object_with_error(
commands: &mut Commands,
asset_server: &ResMut<AssetServer>,
blender_file: &str,
root_object_name: &str,
spawn_children: bool,
parent_transform: Option<Transform>,
) -> anyhow::Result<()> {
let blend = Blend::from_path(
std::env::current_dir()
.unwrap()
.join(std::path::PathBuf::from("assets").join(blender_file)),
);
let obj = match get_object_by_name(&blend, format!("OB{}", root_object_name).as_str()) {
Some(o) => o,
None => {
return Err(anyhow::Error::new(BevyBlenderError::MissingAsset {
asset_name: root_object_name.into(),
blend_file: blender_file.into(),
}));
}
};
let mesh = asset_server.load(
format!(
"{}#{}",
blender_file,
obj.get("data").get("id").get_string("name")
)
.as_str(),
);
let material: Handle<StandardMaterial> = if obj.get("data").is_valid("mat") {
let mut materials = obj.get("data").get_iter("mat");
match materials.next() {
None => asset_server
.load(format!("{}#{}", blender_file, "bevy_blender_missing_material").as_str()),
Some(material) => asset_server.load(
format!("{}#{}", blender_file, material.get("id").get_string("name")).as_str(),
),
}
} else {
asset_server.load(format!("{}#{}", blender_file, "bevy_blender_missing_material").as_str())
};
let world_matrix = get_world_matrix(&obj);
let transform = match parent_transform {
Some(t) => t,
None => {
let corrected_matrix = right_hand_zup_to_right_hand_yup(&world_matrix);
Transform::from_matrix(corrected_matrix)
}
};
let bundle = BlenderObjectBundle {
mesh,
material,
transform,
..Default::default()
};
commands.spawn(bundle).with_children(|parent| {
if !spawn_children {
return;
}
for child in get_children(&blend, format!("OB{}", root_object_name).as_str()) {
spawn_children_objects(
parent,
asset_server,
&blend,
blender_file,
child,
world_matrix,
);
}
});
Ok(())
}
fn spawn_children_objects(
builder: &mut ChildBuilder,
asset_server: &ResMut<AssetServer>,
blend: &Blend,
blender_file: &str,
obj: Instance,
parent_matrix: Mat4,
) {
let mesh = asset_server.load(
format!(
"{}#{}",
blender_file,
obj.get("data").get("id").get_string("name")
)
.as_str(),
);
let mut materials = obj.get("data").get_iter("mat");
let material: Handle<StandardMaterial> = match materials.next() {
None => asset_server
.load(format!("{}#{}", blender_file, "bevy_blender_missing_material").as_str()),
Some(material) => asset_server
.load(format!("{}#{}", blender_file, material.get("id").get_string("name")).as_str()),
};
let world_matrix = get_world_matrix(&obj);
let local_matrix = parent_matrix.inverse().mul_mat4(&world_matrix);
let corrected_local_matrix = right_hand_zup_to_right_hand_yup(&local_matrix);
let transform = Transform::from_matrix(corrected_local_matrix);
let bundle = BlenderObjectBundle {
mesh,
material,
transform,
..Default::default()
};
builder.spawn(bundle).with_children(|parent| {
for child in get_children(&blend, obj.get("id").get_string("name").as_str()) {
spawn_children_objects(
parent,
asset_server,
&blend,
blender_file,
child,
world_matrix,
);
}
});
}