mirror of
https://github.com/IllusionMan1212/3dobs.git
synced 2026-10-24 10:12:35 +00:00
importer/stl: binary stl loader is implemented
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
- [x] Help menu
|
- [x] Help menu
|
||||||
- [x] About window
|
- [x] About window
|
||||||
- [ ] File logger
|
- [ ] File logger
|
||||||
|
- [ ] Logger should log to file, console and stdout
|
||||||
- [x] Move console to be under objects
|
- [x] Move console to be under objects
|
||||||
- [x] Don't have the viewport window be scrollable
|
- [x] Don't have the viewport window be scrollable
|
||||||
- [x] Object rotation with left click + mouse movement (very rudimentary implementation, but works)
|
- [x] Object rotation with left click + mouse movement (very rudimentary implementation, but works)
|
||||||
|
|||||||
+14
-6
@@ -1,8 +1,9 @@
|
|||||||
mod obj;
|
mod obj;
|
||||||
|
mod stl;
|
||||||
|
|
||||||
use std::{path::PathBuf, collections::HashMap};
|
use std::{path::PathBuf, collections::HashMap};
|
||||||
|
|
||||||
use crate::{mesh::Vertex, aabb::AABB};
|
use crate::{mesh::Vertex, aabb::AABB, utils::SupportedFileExtensions};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum TextureType {
|
pub enum TextureType {
|
||||||
@@ -86,13 +87,20 @@ pub struct Object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_from_file(path: &PathBuf) -> Result<Object, Box<dyn std::error::Error>> {
|
pub fn load_from_file(path: &PathBuf) -> Result<Object, Box<dyn std::error::Error>> {
|
||||||
// FIXME: don't explode if to_str() fails
|
let path_str = match path.to_str() {
|
||||||
let path_str = path.to_str().unwrap();
|
Some(s) => s,
|
||||||
|
None => return Err("Failed to convert path to string".into())
|
||||||
|
};
|
||||||
|
|
||||||
let file = std::fs::File::open(path_str)?;
|
let file = std::fs::File::open(path_str)?;
|
||||||
// TODO: support both stl and obj based on file extension
|
// TODO: if no extension, then test for binary STL magic bytes
|
||||||
// if no extension, then test for binary STL magic bytes
|
|
||||||
// if no magic bytes, then try to guess based on the first line of text in the file
|
// if no magic bytes, then try to guess based on the first line of text in the file
|
||||||
|
|
||||||
let obj = obj::load_obj(path, file)?;
|
let obj = match SupportedFileExtensions::from_str(path.extension().unwrap().to_str().unwrap()) {
|
||||||
|
Some(SupportedFileExtensions::STL) => stl::load_stl(file)?,
|
||||||
|
Some(SupportedFileExtensions::OBJ) => obj::load_obj(path, file)?,
|
||||||
|
_ => panic!("Unsupported file extension: {}", path_str),
|
||||||
|
};
|
||||||
|
|
||||||
Ok(obj)
|
Ok(obj)
|
||||||
}
|
}
|
||||||
|
|||||||
+150
-21
@@ -1,27 +1,156 @@
|
|||||||
struct STLHeader {
|
use std::io::{Read, Seek, BufReader};
|
||||||
header: [u8; 80],
|
|
||||||
num_triangles: u32,
|
use crate::{importer::ObjMesh, importer::Object, importer::Material, aabb::AABB, mesh::Vertex};
|
||||||
|
|
||||||
|
const STL_HEADER_SIZE: u64 = 80;
|
||||||
|
// might not be enough since it's not guaranteed. we'll see
|
||||||
|
const ASCII_STL_MAGIC: [u8; 11] = [0x73, 0x6F, 0x6C, 0x69, 0x64, 0x20, 0x61, 0x73, 0x63, 0x69, 0x69]; // "solid ascii"
|
||||||
|
|
||||||
|
#[repr(packed(2))]
|
||||||
|
struct STLTriangle {
|
||||||
|
normal: glm::Vec3,
|
||||||
|
verts: [glm::Vec3; 3],
|
||||||
|
attribute_byte_count: u16
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Vertex {
|
#[derive(Debug)]
|
||||||
x: f32,
|
struct TrianglesIter<R: Read> {
|
||||||
y: f32,
|
reader: R,
|
||||||
z: f32,
|
buf: Vec<u8>,
|
||||||
|
triangles_to_read: usize,
|
||||||
|
triangles_read: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Triangle {
|
impl<R: Read> TrianglesIter<R> {
|
||||||
normal: [f32; 3],
|
fn new(reader: R, triangles_to_read: usize) -> Self {
|
||||||
vertices: [Vertex; 3],
|
TrianglesIter {
|
||||||
attribute_byte_count: u16,
|
reader,
|
||||||
}
|
buf: vec![0u8; 50],
|
||||||
|
triangles_to_read,
|
||||||
struct STL {
|
triangles_read: 0,
|
||||||
header: STLHeader,
|
}
|
||||||
triangles: Vec<Triangle>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl STL {
|
|
||||||
pub fn new(stl: Vec<u8>) -> () {
|
|
||||||
// TODO:
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<R: Read> Iterator for TrianglesIter<R> {
|
||||||
|
type Item = STLTriangle;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<STLTriangle> {
|
||||||
|
if self.triangles_read >= self.triangles_to_read {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let _ = self.reader.read_exact(&mut self.buf);
|
||||||
|
|
||||||
|
self.triangles_read += 1;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let normal = (self.buf[0..12].as_ptr() as *const glm::Vec3).read();
|
||||||
|
let verts = [
|
||||||
|
(self.buf[12..24].as_ptr() as *const glm::Vec3).read(),
|
||||||
|
(self.buf[24..36].as_ptr() as *const glm::Vec3).read(),
|
||||||
|
(self.buf[36..48].as_ptr() as *const glm::Vec3).read(),
|
||||||
|
];
|
||||||
|
let abc = u16::from_le_bytes([self.buf[48], self.buf[49]]);
|
||||||
|
|
||||||
|
Some(STLTriangle{
|
||||||
|
verts,
|
||||||
|
normal,
|
||||||
|
attribute_byte_count: abc
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_ascii_stl(file: std::fs::File) -> Result<Object, Box<dyn std::error::Error>> {
|
||||||
|
// TODO:
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_binary_stl(mut file: std::fs::File) -> Result<Object, Box<dyn std::error::Error>> {
|
||||||
|
// skip header for now
|
||||||
|
file.seek(std::io::SeekFrom::Start(STL_HEADER_SIZE))?;
|
||||||
|
|
||||||
|
let mut buf: [u8; 4] = [0; 4];
|
||||||
|
file.read(&mut buf)?;
|
||||||
|
let tri_count: u32 = u32::from_le_bytes(buf);
|
||||||
|
|
||||||
|
let mut min_aabb = glm::vec3(f32::MAX, f32::MAX, f32::MAX);
|
||||||
|
let mut max_aabb = glm::vec3(f32::MIN, f32::MIN, f32::MIN);
|
||||||
|
let mut vertices = Vec::new();
|
||||||
|
let mut indices = Vec::new();
|
||||||
|
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
|
||||||
|
let triangles_reader = TrianglesIter::new(reader, tri_count as usize);
|
||||||
|
|
||||||
|
let tex_coords = glm::vec2(0.0, 0.0);
|
||||||
|
for (i, triangle) in triangles_reader.enumerate() {
|
||||||
|
for vert in triangle.verts {
|
||||||
|
vertices.push(Vertex{
|
||||||
|
position: glm::vec3(vert.x, vert.y, vert.z),
|
||||||
|
normal: glm::vec3(triangle.normal.x, triangle.normal.y, triangle.normal.z),
|
||||||
|
tex_coords,
|
||||||
|
});
|
||||||
|
|
||||||
|
if vert.x < min_aabb.x {
|
||||||
|
min_aabb.x = vert.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if vert.y < min_aabb.y {
|
||||||
|
min_aabb.y = vert.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if vert.z < min_aabb.z {
|
||||||
|
min_aabb.z = vert.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
if vert.x > max_aabb.x {
|
||||||
|
max_aabb.x = vert.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if vert.y > max_aabb.y {
|
||||||
|
max_aabb.y = vert.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if vert.z > max_aabb.z {
|
||||||
|
max_aabb.z = vert.z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
indices.push(i as u32*3);
|
||||||
|
indices.push(i as u32*3 + 1);
|
||||||
|
indices.push(i as u32*3 + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
let aabb = AABB::new(min_aabb, max_aabb);
|
||||||
|
|
||||||
|
Ok(Object{
|
||||||
|
name: "default_object".to_string(),
|
||||||
|
meshes: vec![ObjMesh{
|
||||||
|
name: "default_mesh".to_string(),
|
||||||
|
vertices,
|
||||||
|
indices,
|
||||||
|
material: Some(Material::default())
|
||||||
|
}],
|
||||||
|
aabb,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_stl(mut file: std::fs::File) -> Result<Object, Box<dyn std::error::Error>> {
|
||||||
|
let mut buf: [u8; 11] = [0; 11];
|
||||||
|
let _ = file.read_exact(&mut buf);
|
||||||
|
_ = file.seek(std::io::SeekFrom::Start(0));
|
||||||
|
let is_ascii = buf == ASCII_STL_MAGIC;
|
||||||
|
|
||||||
|
let obj = if is_ascii {
|
||||||
|
parse_ascii_stl(file)?
|
||||||
|
} else {
|
||||||
|
parse_binary_stl(file)?
|
||||||
|
};
|
||||||
|
let now = std::time::Instant::now();
|
||||||
|
let obj = parse_binary_stl(file)?;
|
||||||
|
let elapsed = now.elapsed();
|
||||||
|
println!("STL file loaded in {} ms", elapsed.as_millis());
|
||||||
|
|
||||||
|
Ok(obj)
|
||||||
|
}
|
||||||
|
|||||||
+23
-4
@@ -6,7 +6,20 @@ use anyhow::{Result, Context};
|
|||||||
|
|
||||||
use crate::{log, model, ui, importer};
|
use crate::{log, model, ui, importer};
|
||||||
|
|
||||||
const SUPPORTED_FILE_EXTENSIONS: [&str; 2] = ["obj", "stl"];
|
pub enum SupportedFileExtensions {
|
||||||
|
OBJ,
|
||||||
|
STL
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SupportedFileExtensions {
|
||||||
|
pub fn from_str(s: &str) -> Option<Self> {
|
||||||
|
match s.to_ascii_lowercase().as_str() {
|
||||||
|
"obj" => Some(Self::OBJ),
|
||||||
|
"stl" => Some(Self::STL),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load_texture(path: &str) -> Result<u32> {
|
pub fn load_texture(path: &str) -> Result<u32> {
|
||||||
let tex = image::io::Reader::open(path)
|
let tex = image::io::Reader::open(path)
|
||||||
@@ -56,9 +69,15 @@ pub fn import_models_from_paths(paths: &Vec<PathBuf>, state: &mut ui::ui::State)
|
|||||||
state.logger.log(&format!("Skipping directory \"{}\"", filename.unwrap().to_str().unwrap()), log::LogLevel::Info);
|
state.logger.log(&format!("Skipping directory \"{}\"", filename.unwrap().to_str().unwrap()), log::LogLevel::Info);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(ext) = model_path.extension() {
|
match model_path.extension() {
|
||||||
if !SUPPORTED_FILE_EXTENSIONS.contains(&ext.to_ascii_lowercase().to_str().unwrap()) {
|
Some(ext) => {
|
||||||
state.logger.log(&format!("Skipping file \"{}\" because it is not an OBJ or STL file", filename.unwrap().to_str().unwrap()), log::LogLevel::Info);
|
if SupportedFileExtensions::from_str(ext.to_str().unwrap()).is_none() {
|
||||||
|
state.logger.log(&format!("Skipping file \"{}\" because it is not an OBJ or STL file", filename.unwrap().to_str().unwrap()), log::LogLevel::Info);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
state.logger.log(&format!("Skipping file \"{}\" because it has no extension", filename.unwrap().to_str().unwrap()), log::LogLevel::Info);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user