40个版本 (18个稳定版)
4.0.2 | 2024年3月27日 |
---|---|
4.0.0 | 2023年4月29日 |
3.2.5 | 2023年4月4日 |
3.2.4 | 2023年2月3日 |
0.0.9 | 2015年6月26日 |
#3 在 游戏开发 中
21,992 每月下载量
用于 72 个 库(直接使用46个)
115KB
2K SLoC
tobj
– Tiny OBJ Loader
受Syoyo的出色tinyobjloader
启发。旨在成为加载OBJ文件的一个简单且轻量级的解决方案。
仅返回两个包含加载模型和材质的Vec
。
三角化
网格可以在运行时三角化,也可以保持原样。
仅支持可以简单转换为三角扇形的面。任意多边形可能不会按预期行为。最佳解决方案是在建模软件中将网格转换为仅由三角形组成。
可选 - 法线、纹理坐标和顶点颜色
假设所有网格至少具有位置,但法线、纹理坐标和顶点颜色是可选的。
如果没有找到法线、纹理坐标或顶点颜色,则相应的Vec
将为空。
平面数据
值以浮点数的形式存储在平面Vec
中。
例如,Mesh
的positions
成员将包含[x, y, z, x, y, z, ...]
,您可以使用它来执行任何操作。
索引
索引也会被加载,并且可能会重用网格中已经存在的顶点,这些数据存储在 indices
成员中。
当 Mesh
包含每个顶点每个面的法线或纹理坐标时,可以通过 single_index
标志将位置重复以实现每个顶点每个面的效果。这可能会改变拓扑结构(即使它们的顶点在空间中仍然共享位置,面也可能变得不连接)。
默认情况下,会创建法线和纹理坐标的单独索引。这也保证了当后者被指定为每个顶点每个面时,Mesh
的拓扑结构不会改变。
材质
也支持标准 MTL
属性。任何未识别的参数都将存储在包含未识别参数及其值的键值对的 HashMap
中。
特性
-
ahash
– 默认启用。在读取文件和合并顶点时使用AHashMap
进行哈希。要禁用并使用较慢的HashMap
,请在Cargo.toml
中取消设置默认特性。[dependencies.tobj] default-features = false
-
merging
– 在导入期间添加了对在断开连接的面之间合并相同顶点位置的支持。警告:此功能使用 const generics,因此至少需要一个
beta
工具链才能构建。 -
reordering
– 添加了对重新排列法线和纹理坐标索引的支持。 -
async
– 添加了对从缓冲区异步加载 obj 文件以及异步材质加载器的支持。在不支持阻塞 IO 的环境中很有用(例如 WebAssembly)。
文档
可以在 这里 找到 Rust 文档。
安装
将 crate 添加为 Cargo.toml
中的依赖项,然后您就设置好了!
示例
打印网格示例(下面也有)从命令行加载 OBJ
文件,并打印出关于其面、顶点和材质的一些信息。
fn main() {
let obj_file = std::env::args()
.skip(1)
.next()
.expect("A .obj file to print is required");
let (models, materials) =
tobj::load_obj(
&obj_file,
&tobj::LoadOptions::default()
)
.expect("Failed to OBJ load file");
// Note: If you don't mind missing the materials, you can generate a default.
let materials = materials.expect("Failed to load MTL file");
println!("Number of models = {}", models.len());
println!("Number of materials = {}", materials.len());
for (i, m) in models.iter().enumerate() {
let mesh = &m.mesh;
println!("");
println!("model[{}].name = \'{}\'", i, m.name);
println!("model[{}].mesh.material_id = {:?}", i, mesh.material_id);
println!(
"model[{}].face_count = {}",
i,
mesh.face_arities.len()
);
let mut next_face = 0;
for face in 0..mesh.face_arities.len() {
let end = next_face + mesh.face_arities[face] as usize;
let face_indices = &mesh.indices[next_face..end];
println!(" face[{}].indices = {:?}", face, face_indices);
if !mesh.texcoord_indices.is_empty() {
let texcoord_face_indices = &mesh.texcoord_indices[next_face..end];
println!(
" face[{}].texcoord_indices = {:?}",
face, texcoord_face_indices
);
}
if !mesh.normal_indices.is_empty() {
let normal_face_indices = &mesh.normal_indices[next_face..end];
println!(
" face[{}].normal_indices = {:?}",
face, normal_face_indices
);
}
next_face = end;
}
// Normals and texture coordinates are also loaded, but not printed in
// this example.
println!(
"model[{}].positions = {}",
i,
mesh.positions.len() / 3
);
assert!(mesh.positions.len() % 3 == 0);
for vtx in 0..mesh.positions.len() / 3 {
println!(
" position[{}] = ({}, {}, {})",
vtx,
mesh.positions[3 * vtx],
mesh.positions[3 * vtx + 1],
mesh.positions[3 * vtx + 2]
);
}
}
for (i, m) in materials.iter().enumerate() {
println!("material[{}].name = \'{}\'", i, m.name);
println!(
" material.Ka = ({}, {}, {})",
m.ambient[0], m.ambient[1], m.ambient[2]
);
println!(
" material.Kd = ({}, {}, {})",
m.diffuse[0], m.diffuse[1], m.diffuse[2]
);
println!(
" material.Ks = ({}, {}, {})",
m.specular[0], m.specular[1], m.specular[2]
);
println!(" material.Ns = {}", m.shininess);
println!(" material.d = {}", m.dissolve);
println!(" material.map_Ka = {}", m.ambient_texture);
println!(" material.map_Kd = {}", m.diffuse_texture);
println!(" material.map_Ks = {}", m.specular_texture);
println!(" material.map_Ns = {}", m.shininess_texture);
println!(" material.map_Bump = {}", m.normal_texture);
println!(" material.map_d = {}", m.dissolve_texture);
for (k, v) in &m.unknown_param {
println!(" material.{} = {}", k, v);
}
}
}
依赖项
~0.6–1MB
~14K SLoC