2020-05-15 23:55:44 +00:00
|
|
|
use bevy_render::{
|
2020-09-25 19:13:01 +00:00
|
|
|
mesh::{Indices, Mesh, VertexAttribute},
|
2020-07-17 01:26:21 +00:00
|
|
|
pipeline::PrimitiveTopology,
|
2020-05-15 23:55:44 +00:00
|
|
|
};
|
|
|
|
|
2020-05-16 07:27:30 +00:00
|
|
|
use anyhow::Result;
|
2020-05-17 03:18:30 +00:00
|
|
|
use bevy_asset::AssetLoader;
|
2020-09-25 21:29:30 +00:00
|
|
|
use gltf::{buffer::Source, mesh::Mode};
|
2020-05-15 23:55:44 +00:00
|
|
|
use std::{fs, io, path::Path};
|
|
|
|
use thiserror::Error;
|
|
|
|
|
2020-08-09 23:13:04 +00:00
|
|
|
/// Loads meshes from GLTF files into Mesh assets
|
|
|
|
///
|
|
|
|
/// NOTE: eventually this will loading into Scenes instead of Meshes
|
2020-10-08 18:43:01 +00:00
|
|
|
#[derive(Debug, Default)]
|
2020-05-15 23:55:44 +00:00
|
|
|
pub struct GltfLoader;
|
|
|
|
|
|
|
|
impl AssetLoader<Mesh> for GltfLoader {
|
2020-05-17 03:18:30 +00:00
|
|
|
fn from_bytes(&self, asset_path: &Path, bytes: Vec<u8>) -> Result<Mesh> {
|
2020-05-15 23:55:44 +00:00
|
|
|
let mesh = load_gltf(asset_path, bytes)?;
|
|
|
|
Ok(mesh)
|
|
|
|
}
|
2020-07-28 21:24:03 +00:00
|
|
|
|
2020-05-15 23:55:44 +00:00
|
|
|
fn extensions(&self) -> &[&str] {
|
2020-08-25 23:55:08 +00:00
|
|
|
static EXTENSIONS: &[&str] = &["gltf", "glb"];
|
2020-05-15 23:55:44 +00:00
|
|
|
EXTENSIONS
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-09 23:13:04 +00:00
|
|
|
/// An error that occurs when loading a GLTF file
|
2020-05-15 23:55:44 +00:00
|
|
|
#[derive(Error, Debug)]
|
|
|
|
pub enum GltfError {
|
|
|
|
#[error("Unsupported primitive mode.")]
|
|
|
|
UnsupportedPrimitive { mode: Mode },
|
|
|
|
#[error("Invalid GLTF file.")]
|
|
|
|
Gltf(#[from] gltf::Error),
|
|
|
|
#[error("Failed to load file.")]
|
|
|
|
Io(#[from] io::Error),
|
2020-08-25 23:55:08 +00:00
|
|
|
#[error("Binary blob is missing.")]
|
|
|
|
MissingBlob,
|
2020-08-12 17:23:54 +00:00
|
|
|
#[error("Failed to decode base64 mesh data.")]
|
|
|
|
Base64Decode(#[from] base64::DecodeError),
|
|
|
|
#[error("Unsupported buffer format.")]
|
|
|
|
BufferFormatUnsupported,
|
2020-05-15 23:55:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn get_primitive_topology(mode: Mode) -> Result<PrimitiveTopology, GltfError> {
|
|
|
|
match mode {
|
|
|
|
Mode::Points => Ok(PrimitiveTopology::PointList),
|
|
|
|
Mode::Lines => Ok(PrimitiveTopology::LineList),
|
|
|
|
Mode::LineStrip => Ok(PrimitiveTopology::LineStrip),
|
|
|
|
Mode::Triangles => Ok(PrimitiveTopology::TriangleList),
|
|
|
|
Mode::TriangleStrip => Ok(PrimitiveTopology::TriangleStrip),
|
2020-08-16 07:30:04 +00:00
|
|
|
mode => Err(GltfError::UnsupportedPrimitive { mode }),
|
2020-05-15 23:55:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-06 07:12:38 +00:00
|
|
|
// TODO: this should return a scene
|
2020-05-17 03:18:30 +00:00
|
|
|
pub fn load_gltf(asset_path: &Path, bytes: Vec<u8>) -> Result<Mesh, GltfError> {
|
2020-05-15 23:55:44 +00:00
|
|
|
let gltf = gltf::Gltf::from_slice(&bytes)?;
|
2020-08-25 23:55:08 +00:00
|
|
|
let buffer_data = load_buffers(&gltf, asset_path)?;
|
2020-05-15 23:55:44 +00:00
|
|
|
for scene in gltf.scenes() {
|
2020-08-16 07:30:04 +00:00
|
|
|
if let Some(node) = scene.nodes().next() {
|
2020-05-15 23:55:44 +00:00
|
|
|
return Ok(load_node(&buffer_data, &node, 1)?);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: remove this when full gltf support is added
|
|
|
|
panic!("no mesh found!")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn load_node(buffer_data: &[Vec<u8>], node: &gltf::Node, depth: i32) -> Result<Mesh, GltfError> {
|
|
|
|
if let Some(mesh) = node.mesh() {
|
2020-08-16 07:30:04 +00:00
|
|
|
if let Some(primitive) = mesh.primitives().next() {
|
2020-05-15 23:55:44 +00:00
|
|
|
let reader = primitive.reader(|buffer| Some(&buffer_data[buffer.index()]));
|
|
|
|
let primitive_topology = get_primitive_topology(primitive.mode())?;
|
|
|
|
let mut mesh = Mesh::new(primitive_topology);
|
2020-08-16 07:30:04 +00:00
|
|
|
|
2020-08-20 21:06:16 +00:00
|
|
|
if let Some(vertex_attribute) = reader
|
|
|
|
.read_positions()
|
|
|
|
.map(|v| VertexAttribute::position(v.collect()))
|
|
|
|
{
|
2020-08-16 07:30:04 +00:00
|
|
|
mesh.attributes.push(vertex_attribute);
|
|
|
|
}
|
|
|
|
|
2020-08-20 21:06:16 +00:00
|
|
|
if let Some(vertex_attribute) = reader
|
|
|
|
.read_normals()
|
|
|
|
.map(|v| VertexAttribute::normal(v.collect()))
|
|
|
|
{
|
2020-08-16 07:30:04 +00:00
|
|
|
mesh.attributes.push(vertex_attribute);
|
|
|
|
}
|
|
|
|
|
2020-08-20 21:06:16 +00:00
|
|
|
if let Some(vertex_attribute) = reader
|
|
|
|
.read_tex_coords(0)
|
|
|
|
.map(|v| VertexAttribute::uv(v.into_f32().collect()))
|
|
|
|
{
|
2020-08-16 07:30:04 +00:00
|
|
|
mesh.attributes.push(vertex_attribute);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(indices) = reader.read_indices() {
|
2020-09-25 21:29:30 +00:00
|
|
|
mesh.indices = Some(Indices::U32(indices.into_u32().collect()));
|
|
|
|
}
|
2020-05-15 23:55:44 +00:00
|
|
|
|
|
|
|
return Ok(mesh);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-16 07:30:04 +00:00
|
|
|
if let Some(child) = node.children().next() {
|
2020-05-15 23:55:44 +00:00
|
|
|
return Ok(load_node(buffer_data, &child, depth + 1)?);
|
|
|
|
}
|
|
|
|
|
|
|
|
panic!("failed to find mesh")
|
|
|
|
}
|
|
|
|
|
2020-08-25 23:55:08 +00:00
|
|
|
fn load_buffers(gltf: &gltf::Gltf, asset_path: &Path) -> Result<Vec<Vec<u8>>, GltfError> {
|
2020-08-12 17:23:54 +00:00
|
|
|
const OCTET_STREAM_URI: &str = "data:application/octet-stream;base64,";
|
|
|
|
|
2020-05-15 23:55:44 +00:00
|
|
|
let mut buffer_data = Vec::new();
|
2020-08-25 23:55:08 +00:00
|
|
|
for buffer in gltf.buffers() {
|
2020-05-15 23:55:44 +00:00
|
|
|
match buffer.source() {
|
|
|
|
Source::Uri(uri) => {
|
|
|
|
if uri.starts_with("data:") {
|
2020-08-12 17:23:54 +00:00
|
|
|
if uri.starts_with(OCTET_STREAM_URI) {
|
|
|
|
buffer_data.push(base64::decode(&uri[OCTET_STREAM_URI.len()..])?);
|
|
|
|
} else {
|
|
|
|
return Err(GltfError::BufferFormatUnsupported);
|
|
|
|
}
|
2020-05-15 23:55:44 +00:00
|
|
|
} else {
|
2020-05-17 03:18:30 +00:00
|
|
|
let buffer_path = asset_path.parent().unwrap().join(uri);
|
2020-05-15 23:55:44 +00:00
|
|
|
let buffer_bytes = fs::read(buffer_path)?;
|
|
|
|
buffer_data.push(buffer_bytes);
|
|
|
|
}
|
|
|
|
}
|
2020-08-25 23:55:08 +00:00
|
|
|
Source::Bin => {
|
|
|
|
if let Some(blob) = gltf.blob.as_deref() {
|
|
|
|
buffer_data.push(blob.into());
|
|
|
|
} else {
|
|
|
|
return Err(GltfError::MissingBlob);
|
|
|
|
}
|
|
|
|
}
|
2020-05-15 23:55:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(buffer_data)
|
|
|
|
}
|