mirror of
https://github.com/IllusionMan1212/3dobs.git
synced 2026-10-24 10:12:35 +00:00
importer/collada: WIP
This commit is contained in:
Generated
+16
@@ -1456,6 +1456,7 @@ dependencies = [
|
|||||||
"rfd",
|
"rfd",
|
||||||
"serde",
|
"serde",
|
||||||
"simplelog",
|
"simplelog",
|
||||||
|
"xml",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1830,6 +1831,21 @@ dependencies = [
|
|||||||
"memchr 2.6.4",
|
"memchr 2.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xml"
|
||||||
|
version = "0.8.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5cb5b0add8d5aaa69357818de5f3b60104a62ef345dca81d0947e2148347a7f7"
|
||||||
|
dependencies = [
|
||||||
|
"xml-rs",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xml-rs"
|
||||||
|
version = "0.8.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zune-inflate"
|
name = "zune-inflate"
|
||||||
version = "0.2.54"
|
version = "0.2.54"
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ bincode = "1.3.3"
|
|||||||
fs4 = "0.7.0"
|
fs4 = "0.7.0"
|
||||||
simplelog = "0.12.1"
|
simplelog = "0.12.1"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
|
xml = "0.8.10"
|
||||||
|
|
||||||
[dependencies.imgui]
|
[dependencies.imgui]
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
|||||||
@@ -0,0 +1,370 @@
|
|||||||
|
use std::{io::BufReader, collections::HashMap};
|
||||||
|
|
||||||
|
use crate::{importer::{Object, ObjMesh}, aabb::AABB, mesh::{Mesh, Vertex}};
|
||||||
|
|
||||||
|
use log::{info, warn};
|
||||||
|
use xml::reader::{EventReader, XmlEvent};
|
||||||
|
|
||||||
|
enum ColladaElement {
|
||||||
|
// Asset,
|
||||||
|
// LibraryAnimations,
|
||||||
|
// LibraryAnimationClips,
|
||||||
|
// LibraryCameras,
|
||||||
|
// LibraryControllers,
|
||||||
|
// LibraryForceFields,
|
||||||
|
// LibraryImages,
|
||||||
|
// LibraryLights,
|
||||||
|
// LibraryNodes,
|
||||||
|
// LibraryPhysicsMaterials,
|
||||||
|
// LibraryPhysicsModels,
|
||||||
|
// LibraryPhysicsScenes,
|
||||||
|
// LibraryVisualScenes,
|
||||||
|
// Scene,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum SupportedElement {
|
||||||
|
None,
|
||||||
|
|
||||||
|
Unit,
|
||||||
|
UpAxis,
|
||||||
|
|
||||||
|
LibraryMaterials,
|
||||||
|
Material,
|
||||||
|
InstanceEffect,
|
||||||
|
|
||||||
|
LibraryEffects,
|
||||||
|
Effect,
|
||||||
|
Technique, // might remove this
|
||||||
|
Diffuse, // vec4
|
||||||
|
Shininess,
|
||||||
|
|
||||||
|
LibraryGeometries,
|
||||||
|
Geometry,
|
||||||
|
Mesh,
|
||||||
|
Source,
|
||||||
|
Input,
|
||||||
|
FloatArray,
|
||||||
|
Polylist,
|
||||||
|
VCount,
|
||||||
|
Triangles,
|
||||||
|
P,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SupportedElement {
|
||||||
|
fn from_str(s: &str) -> Option<Self> {
|
||||||
|
match s {
|
||||||
|
"unit" => Some(Self::Unit),
|
||||||
|
"up_axis" => Some(Self::UpAxis),
|
||||||
|
|
||||||
|
"library_materials" => Some(Self::LibraryMaterials),
|
||||||
|
"material" => Some(Self::Material),
|
||||||
|
"instance_effect" => Some(Self::InstanceEffect),
|
||||||
|
|
||||||
|
"library_effects" => Some(Self::LibraryEffects),
|
||||||
|
"effect" => Some(Self::Effect),
|
||||||
|
"technique" => Some(Self::Technique),
|
||||||
|
"diffuse" => Some(Self::Diffuse),
|
||||||
|
"shininess" => Some(Self::Shininess),
|
||||||
|
|
||||||
|
"library_geometries" => Some(Self::LibraryGeometries),
|
||||||
|
"geometry" => Some(Self::Geometry),
|
||||||
|
"mesh" => Some(Self::Mesh),
|
||||||
|
"source" => Some(Self::Source),
|
||||||
|
"input" => Some(Self::Input),
|
||||||
|
"float_array" => Some(Self::FloatArray),
|
||||||
|
"polylist" => Some(Self::Polylist),
|
||||||
|
"vcount" => Some(Self::VCount),
|
||||||
|
"triangles" => Some(Self::Triangles),
|
||||||
|
"p" => Some(Self::P),
|
||||||
|
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_dae(file: std::fs::File) -> Result<Object, Box<dyn std::error::Error>> {
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
let parser = EventReader::new(reader);
|
||||||
|
|
||||||
|
let mut chunk_p_by = 0;
|
||||||
|
let mut current_face_count = 0;
|
||||||
|
|
||||||
|
let mut current_source_id = String::new();
|
||||||
|
let mut current_element = SupportedElement::None;
|
||||||
|
let mut sources = HashMap::<String, Vec<f32>>::new();
|
||||||
|
let mut vcount = Vec::new();
|
||||||
|
let mut positions = Vec::new();
|
||||||
|
let mut normals = Vec::new();
|
||||||
|
let mut tex_coords = Vec::new();
|
||||||
|
|
||||||
|
let mut vertices: Vec<Vertex> = Vec::new();
|
||||||
|
let mut indices = Vec::new();
|
||||||
|
// let meshes = Vec::new();
|
||||||
|
// let materials = Vec::new();
|
||||||
|
|
||||||
|
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 now = std::time::Instant::now();
|
||||||
|
for e in parser {
|
||||||
|
match e {
|
||||||
|
Ok(XmlEvent::StartElement { name, attributes, .. }) => {
|
||||||
|
current_element = match SupportedElement::from_str(&name.local_name) {
|
||||||
|
Some(element) => element,
|
||||||
|
None => {
|
||||||
|
// warn!("Unsupported element: {}", name.local_name);
|
||||||
|
SupportedElement::None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match current_element {
|
||||||
|
SupportedElement::Unit => {},
|
||||||
|
SupportedElement::UpAxis => {},
|
||||||
|
SupportedElement::LibraryMaterials => {},
|
||||||
|
SupportedElement::Source => {
|
||||||
|
if let Some(source_id) = attributes.into_iter().find(|a| a.name.local_name == "id") {
|
||||||
|
current_source_id = source_id.value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SupportedElement::Input => {
|
||||||
|
let semantic = &attributes.iter().find(|a| a.name.local_name == "semantic").unwrap().value;
|
||||||
|
// strip the # at the beginning of the source id
|
||||||
|
let source_id = &attributes.iter().find(|a| a.name.local_name == "source").unwrap().value[1..];
|
||||||
|
|
||||||
|
match semantic.as_str() {
|
||||||
|
"POSITION" => {
|
||||||
|
positions = sources.get(source_id)
|
||||||
|
.unwrap()
|
||||||
|
.chunks(3)
|
||||||
|
.map(|v| {
|
||||||
|
let vertex = glm::vec3(v[0], v[1], v[2]);
|
||||||
|
|
||||||
|
min_aabb = glm::vec3(
|
||||||
|
min_aabb.x.min(vertex.x),
|
||||||
|
min_aabb.y.min(vertex.y),
|
||||||
|
min_aabb.z.min(vertex.z),
|
||||||
|
);
|
||||||
|
|
||||||
|
max_aabb = glm::vec3(
|
||||||
|
max_aabb.x.max(vertex.x),
|
||||||
|
max_aabb.y.max(vertex.y),
|
||||||
|
max_aabb.z.max(vertex.z),
|
||||||
|
);
|
||||||
|
|
||||||
|
vertex
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
}
|
||||||
|
"NORMAL" => {
|
||||||
|
chunk_p_by += 1;
|
||||||
|
|
||||||
|
normals = sources.get(source_id)
|
||||||
|
.unwrap()
|
||||||
|
.chunks(3)
|
||||||
|
.map(|v| glm::vec3(v[0], v[1], v[2]))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
},
|
||||||
|
"TEXCOORD" => {
|
||||||
|
chunk_p_by += 1;
|
||||||
|
|
||||||
|
tex_coords = sources.get(source_id)
|
||||||
|
.unwrap()
|
||||||
|
.chunks(2)
|
||||||
|
.map(|v| glm::vec2(v[0], v[1]))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
}
|
||||||
|
// ignore this since it's used to refer to the vertices
|
||||||
|
// tag and we already parse POSITION from that
|
||||||
|
"VERTEX" => {
|
||||||
|
chunk_p_by += 1;
|
||||||
|
},
|
||||||
|
_ => { panic!("Unknown <input> semantic: {}", semantic) }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// This element is used to indicate that the vcount
|
||||||
|
// aka vertex count for every face is 3 without having to
|
||||||
|
// explicity write the vcount element
|
||||||
|
SupportedElement::Triangles => {
|
||||||
|
current_face_count = attributes.into_iter().find(|a| a.name.local_name == "count").unwrap().value.parse::<usize>().unwrap();
|
||||||
|
|
||||||
|
// preemptively allocate the exact amount of vertices we want
|
||||||
|
vertices = Vec::with_capacity(current_face_count * 3);
|
||||||
|
|
||||||
|
// fill vcount with all 3s for as many faces as we got
|
||||||
|
// so we can use it later when parsing <p>
|
||||||
|
vcount = vec![3; current_face_count]
|
||||||
|
},
|
||||||
|
// This element can have a varying amount of vertices
|
||||||
|
// per face and the vcount element is required to be present (i think)
|
||||||
|
SupportedElement::Polylist => {
|
||||||
|
current_face_count = attributes.into_iter().find(|a| a.name.local_name == "count").unwrap().value.parse::<usize>().unwrap();
|
||||||
|
|
||||||
|
vcount = Vec::with_capacity(current_face_count);
|
||||||
|
}
|
||||||
|
SupportedElement::Geometry => {
|
||||||
|
// TODO: name attribute for the object? name
|
||||||
|
},
|
||||||
|
_ => {} // Other elements we don't care about (should probably only be None
|
||||||
|
// here and we can nuke the elements that we don't use from the enum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(XmlEvent::EndElement { name }) => {
|
||||||
|
match SupportedElement::from_str(&name.local_name) {
|
||||||
|
Some(SupportedElement::P) => {
|
||||||
|
// reset chunk_by to 0 so we know we're done with the
|
||||||
|
// current polylist
|
||||||
|
chunk_p_by = 0;
|
||||||
|
},
|
||||||
|
_ => { /*warn!("Unsupported element: {}", name.local_name)*/ },
|
||||||
|
}
|
||||||
|
// TODO: each polylist has a material. meaning we split the mesh
|
||||||
|
// into multiple meshes according to the polylists.
|
||||||
|
// Some models don't have polylist and instead have a triangles
|
||||||
|
|
||||||
|
// TODO: if we encounter mesh, we push it to the vector
|
||||||
|
|
||||||
|
// Mesh::new();
|
||||||
|
// println!("End: {}", name);
|
||||||
|
}
|
||||||
|
Ok(XmlEvent::Characters(data)) => {
|
||||||
|
match current_element {
|
||||||
|
SupportedElement::FloatArray => {
|
||||||
|
sources.insert(
|
||||||
|
current_source_id.clone(),
|
||||||
|
data
|
||||||
|
.trim()
|
||||||
|
.lines()
|
||||||
|
.map(|l| l
|
||||||
|
.split_ascii_whitespace()
|
||||||
|
.map(|v| v.parse::<f32>().unwrap())
|
||||||
|
.collect::<Vec<_>>())
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
SupportedElement::P => {
|
||||||
|
// TODO: get current_v_count by looping over the faces??
|
||||||
|
// current_v_count * chunk_p_by that's 1 v count
|
||||||
|
// we also need to triangulate anything with >3 v_count
|
||||||
|
// vertices = data
|
||||||
|
// .trim()
|
||||||
|
// .lines()
|
||||||
|
// .map(|l| l
|
||||||
|
// .split_ascii_whitespace()
|
||||||
|
// .map(|i| i.parse::<usize>().unwrap())
|
||||||
|
// .collect::<Vec<_>>())
|
||||||
|
// .flatten()
|
||||||
|
// .collect::<Vec<_>>()
|
||||||
|
// .chunks(chunk_p_by)
|
||||||
|
// .map(|i| Vertex::new(positions[i[0]], normals[i[1]], tex_coords[i[2]]))
|
||||||
|
// .collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// println!("P: {:?}", vertices.len());
|
||||||
|
|
||||||
|
// println!("data: {}", data);
|
||||||
|
let data = data
|
||||||
|
.trim()
|
||||||
|
.split_ascii_whitespace()
|
||||||
|
.map(|i| i.parse::<usize>().unwrap())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
for i in 0..current_face_count {
|
||||||
|
let current_v_count = vcount.get(i).unwrap();
|
||||||
|
// TODO: triangulate face if vcount >3
|
||||||
|
// TODO: get vertices and indices in one go
|
||||||
|
|
||||||
|
let face = data
|
||||||
|
.chunks(chunk_p_by * current_v_count)
|
||||||
|
.skip(i)
|
||||||
|
.take(1)
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// NOTE: no idea if this is fine and doesn't break things
|
||||||
|
if face.len() == 0 { continue; }
|
||||||
|
|
||||||
|
|
||||||
|
// HACK: this assumes that a face only has vertices
|
||||||
|
// for faces with more, we need to triangulate and then
|
||||||
|
// push the indices properly (dk how yet)
|
||||||
|
indices.push(i as u32 * 3);
|
||||||
|
indices.push(i as u32 * 3 + 1);
|
||||||
|
indices.push(i as u32 * 3 + 2);
|
||||||
|
|
||||||
|
// normals for this face
|
||||||
|
let normals = {
|
||||||
|
// TODO: if we don't have normals. we should generate them
|
||||||
|
// also take v_count into account here so we triangulate
|
||||||
|
// the normals immediate as well ???
|
||||||
|
// the lternative is to do this twice in each branch
|
||||||
|
// of the coming if statement. idk
|
||||||
|
};
|
||||||
|
|
||||||
|
// tex_coords for this face
|
||||||
|
let tex_coords = {
|
||||||
|
// TODO: if we don't have tex_coords. return 0.0, 0.0
|
||||||
|
};
|
||||||
|
|
||||||
|
if *current_v_count > 3 {
|
||||||
|
// triangulate
|
||||||
|
} else {
|
||||||
|
// vertices.push(Vertex::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
vertices.extend(face
|
||||||
|
.chunks(chunk_p_by)
|
||||||
|
// TODO: v[0], v[1], v[2] is bad cuz im not using chunk_p_by
|
||||||
|
.map(|v| Vertex::new(positions[*v[0]], normals[*v[1]], tex_coords[*v[2]]))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SupportedElement::VCount => {
|
||||||
|
vcount = data
|
||||||
|
.trim()
|
||||||
|
.lines()
|
||||||
|
.map(|l| l
|
||||||
|
.split_ascii_whitespace()
|
||||||
|
.map(|c| c.parse::<usize>().unwrap())
|
||||||
|
.collect::<Vec<_>>())
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// println!("vcount: {:?}", vcount);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
// warn!("Unsupported Characters event for element: {:?}", current_element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return Err(Box::new(e));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// println!("vcount: {:?}", vcount);
|
||||||
|
println!("Vertices: {:?}", vertices);
|
||||||
|
println!("Indices: {:?}", indices);
|
||||||
|
// println!("Normals: {:?}", normals);
|
||||||
|
// println!("TexCoords: {:?}", tex_coords);
|
||||||
|
let elapsed = now.elapsed();
|
||||||
|
info!("Loaded in {}ms", elapsed.as_millis());
|
||||||
|
|
||||||
|
todo!();
|
||||||
|
|
||||||
|
let aabb = AABB::new(min_aabb, max_aabb);
|
||||||
|
|
||||||
|
// Ok(Object {
|
||||||
|
// name: "test".to_string(),
|
||||||
|
// meshes: vec![ObjMesh{
|
||||||
|
// name: "test".to_string(),
|
||||||
|
// vertices,
|
||||||
|
// indices,
|
||||||
|
// material: None
|
||||||
|
// }],
|
||||||
|
// aabb
|
||||||
|
// })
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
mod obj;
|
mod obj;
|
||||||
mod stl;
|
mod stl;
|
||||||
|
mod collada;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
@@ -135,6 +136,7 @@ pub fn load_from_file(path: &PathBuf) -> Result<Object, Box<dyn std::error::Erro
|
|||||||
let obj = match SupportedFileExtensions::from_str(path.extension().unwrap().to_str().unwrap()) {
|
let obj = match SupportedFileExtensions::from_str(path.extension().unwrap().to_str().unwrap()) {
|
||||||
Some(SupportedFileExtensions::STL) => stl::load_stl(file)?,
|
Some(SupportedFileExtensions::STL) => stl::load_stl(file)?,
|
||||||
Some(SupportedFileExtensions::OBJ) => obj::load_obj(path, file)?,
|
Some(SupportedFileExtensions::OBJ) => obj::load_obj(path, file)?,
|
||||||
|
Some(SupportedFileExtensions::COLLADA) => collada::load_dae(file)?,
|
||||||
_ => panic!("Unsupported file extension: {}", path_str),
|
_ => panic!("Unsupported file extension: {}", path_str),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -10,7 +10,7 @@ use simplelog::*;
|
|||||||
use threedobs::{shader, ui::ui, utils, ipc};
|
use threedobs::{shader, ui::ui, utils, ipc};
|
||||||
|
|
||||||
fn main() -> anyhow::Result<(), Box<dyn std::error::Error>> {
|
fn main() -> anyhow::Result<(), Box<dyn std::error::Error>> {
|
||||||
let logger = threedobs::logger::WritableLog::default();
|
let in_program_logger = threedobs::logger::WritableLog::default();
|
||||||
|
|
||||||
let log_conf = ConfigBuilder::default()
|
let log_conf = ConfigBuilder::default()
|
||||||
.set_target_level(LevelFilter::Error)
|
.set_target_level(LevelFilter::Error)
|
||||||
@@ -40,7 +40,7 @@ fn main() -> anyhow::Result<(), Box<dyn std::error::Error>> {
|
|||||||
vec![
|
vec![
|
||||||
TermLogger::new(log_level, log_conf.clone(), TerminalMode::Mixed, ColorChoice::Auto),
|
TermLogger::new(log_level, log_conf.clone(), TerminalMode::Mixed, ColorChoice::Auto),
|
||||||
WriteLogger::new(log_level, log_conf, File::create("3dobs.log")?),
|
WriteLogger::new(log_level, log_conf, File::create("3dobs.log")?),
|
||||||
WriteLogger::new(log_level, in_program_log_conf, logger.clone())
|
WriteLogger::new(log_level, in_program_log_conf, in_program_logger.clone())
|
||||||
]
|
]
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ fn main() -> anyhow::Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
let mut state = ui::State{
|
let mut state = ui::State{
|
||||||
settings,
|
settings,
|
||||||
logger,
|
logger: in_program_logger,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -111,9 +111,10 @@ pub fn import_model(state: &mut State) {
|
|||||||
let models = match rfd::FileDialog::new()
|
let models = match rfd::FileDialog::new()
|
||||||
.set_title("Import Model(s)")
|
.set_title("Import Model(s)")
|
||||||
.set_directory("./")
|
.set_directory("./")
|
||||||
.add_filter("All supported files", &["obj", "OBJ", "stl", "STL"])
|
.add_filter("All supported files", &["obj", "OBJ", "stl", "STL", "dae", "DAE"])
|
||||||
.add_filter("Wavefront OBJ (.obj)", &["obj", "OBJ"])
|
.add_filter("Wavefront OBJ (.obj)", &["obj", "OBJ"])
|
||||||
.add_filter("STL (.stl)", &["stl", "STL"])
|
.add_filter("STL (.stl)", &["stl", "STL"])
|
||||||
|
.add_filter("COLLADA (.dae)", &["dae", "DAE"])
|
||||||
.pick_files() {
|
.pick_files() {
|
||||||
Some(m) => m,
|
Some(m) => m,
|
||||||
None => return,
|
None => return,
|
||||||
|
|||||||
+3
-1
@@ -9,7 +9,8 @@ use crate::{model, ui, importer};
|
|||||||
|
|
||||||
pub enum SupportedFileExtensions {
|
pub enum SupportedFileExtensions {
|
||||||
OBJ,
|
OBJ,
|
||||||
STL
|
STL,
|
||||||
|
COLLADA,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SupportedFileExtensions {
|
impl SupportedFileExtensions {
|
||||||
@@ -17,6 +18,7 @@ impl SupportedFileExtensions {
|
|||||||
match s.to_ascii_lowercase().as_str() {
|
match s.to_ascii_lowercase().as_str() {
|
||||||
"obj" => Some(Self::OBJ),
|
"obj" => Some(Self::OBJ),
|
||||||
"stl" => Some(Self::STL),
|
"stl" => Some(Self::STL),
|
||||||
|
"dae" => Some(Self::COLLADA),
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user