Add obj parsing

This commit is contained in:
hal8174 2024-10-02 23:43:13 +02:00
parent 0a70fbd8d4
commit 556f50274d
13 changed files with 1004 additions and 3 deletions

View file

@ -4,5 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
miette = { version = "7.2.0", features = ["fancy"] }
nom = "7.1.3"
ray-tracing-core = { path = "../ray-tracing-core" }
ray-tracing-material = { path = "../ray-tracing-material" }

View file

@ -0,0 +1,10 @@
use miette::Result;
use ray_tracing_scene::parse_obj::ObjData;
fn main() -> Result<()> {
let obj = ObjData::new("obj/cornell_box.obj")?;
dbg!(obj);
Ok(())
}

View file

@ -0,0 +1,43 @@
o box
# left side
v 0.5 -0.5 -0.5
v 0.5 -0.5 0.5
v 0.5 0.5 0.5
v 0.5 0.5 -0.5
f -4 -3 -2 -1
# right side
v -0.5 -0.5 -0.5
v -0.5 -0.5 0.5
v -0.5 0.5 0.5
v -0.5 0.5 -0.5
f -4 -3 -2 -1
# bottom side
v -0.5 -0.5 -0.5
v -0.5 -0.5 0.5
v 0.5 -0.5 0.5
v 0.5 -0.5 -0.5
f -4 -3 -2 -1
# top side
v -0.5 0.5 -0.5
v -0.5 0.5 0.5
v 0.5 0.5 0.5
v 0.5 0.5 -0.5
f -4 -3 -2 -1
# front side
v -0.5 -0.5 -0.5
v -0.5 0.5 -0.5
v 0.5 0.5 -0.5
v 0.5 -0.5 -0.5
f -4 -3 -2 -1
# back side
v -0.5 -0.5 0.5
v -0.5 0.5 0.5
v 0.5 0.5 0.5
v 0.5 -0.5 0.5
f -4 -3 -2 -1

View file

@ -0,0 +1,38 @@
# Blender 4.1.1
# www.blender.org
o box
v -0.500000 -0.500000 0.500000
v -0.500000 0.500000 0.500000
v -0.500000 -0.500000 -0.500000
v -0.500000 0.500000 -0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v 0.500000 0.500000 -0.500000
vn -1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 -1.0000
vn 1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 1.0000
vn -0.0000 -1.0000 -0.0000
vn -0.0000 1.0000 -0.0000
vt 0.375000 0.000000
vt 0.625000 0.000000
vt 0.625000 0.250000
vt 0.375000 0.250000
vt 0.625000 0.500000
vt 0.375000 0.500000
vt 0.625000 0.750000
vt 0.375000 0.750000
vt 0.625000 1.000000
vt 0.375000 1.000000
vt 0.125000 0.500000
vt 0.125000 0.750000
vt 0.875000 0.500000
vt 0.875000 0.750000
s 0
f 1/1/1 2/2/1 4/3/1 3/4/1
f 3/4/2 4/3/2 8/5/2 7/6/2
f 7/6/3 8/5/3 6/7/3 5/8/3
f 5/8/4 6/7/4 2/9/4 1/10/4
f 3/11/5 7/6/5 5/8/5 1/12/5
f 8/5/6 4/13/6 2/14/6 6/7/6

View file

@ -0,0 +1,27 @@
newmtl white
Ka 0 0 0
Kd 1 1 1
Ks 0 0 0
newmtl red
Ka 0 0 0
Kd 1 0 0
Ks 0 0 0
newmtl green
Ka 0 0 0
Kd 0 1 0
Ks 0 0 0
newmtl blue
Ka 0 0 0
Kd 0 0 1
Ks 0 0 0
newmtl light
Ka 20 20 20
Kd 1 1 1
Ks 0 0 0
newmtl glass
type glass

View file

@ -0,0 +1,140 @@
# original cornell box data
mtllib cornell_box.mtl
o floor
usemtl white
v 552.8 0.0 0.0
v 0.0 0.0 0.0
v 0.0 0.0 559.2
v 549.6 0.0 559.2
v 130.0 0.0 65.0
v 82.0 0.0 225.0
v 240.0 0.0 272.0
v 290.0 0.0 114.0
v 423.0 0.0 247.0
v 265.0 0.0 296.0
v 314.0 0.0 456.0
v 472.0 0.0 406.0
f 1 2 3 4
f 8 7 6 5
f 12 11 10 9
o light
usemtl light
v 343.0 548.0 227.0
v 343.0 548.0 332.0
v 213.0 548.0 332.0
v 213.0 548.0 227.0
#f -4 -3 -2 -1
o ceiling
usemtl white
v 556.0 548.8 0.0
v 556.0 548.8 559.2
v 0.0 548.8 559.2
v 0.0 548.8 0.0
f -4 -3 -2 -1
o back_wall
usemtl white
v 549.6 0.0 559.2
v 0.0 0.0 559.2
v 0.0 548.8 559.2
v 556.0 548.8 559.2
f -4 -3 -2 -1
o front_wall
usemtl blue
v 549.6 0.0 0
v 0.0 0.0 0
v 0.0 548.8 0
v 556.0 548.8 0
#f -1 -2 -3 -4
o green_wall
usemtl green
v 0.0 0.0 559.2
v 0.0 0.0 0.0
v 0.0 548.8 0.0
v 0.0 548.8 559.2
f -4 -3 -2 -1
o red_wall
usemtl red
v 552.8 0.0 0.0
v 549.6 0.0 559.2
v 556.0 548.8 559.2
v 556.0 548.8 0.0
f -4 -3 -2 -1
o short_block
usemtl glass
v 130.0 165.0 65.0
v 82.0 165.0 225.0
v 240.0 165.0 272.0
v 290.0 165.0 114.0
f -4 -3 -2 -1
v 290.0 0.0 114.0
v 290.0 165.0 114.0
v 240.0 165.0 272.0
v 240.0 0.0 272.0
f -4 -3 -2 -1
v 130.0 0.0 65.0
v 130.0 165.0 65.0
v 290.0 165.0 114.0
v 290.0 0.0 114.0
f -4 -3 -2 -1
v 82.0 0.0 225.0
v 82.0 165.0 225.0
v 130.0 165.0 65.0
v 130.0 0.0 65.0
f -4 -3 -2 -1
v 240.0 0.0 272.0
v 240.0 165.0 272.0
v 82.0 165.0 225.0
v 82.0 0.0 225.0
f -4 -3 -2 -1
o tall_block
usemtl white
v 423.0 330.0 247.0
v 265.0 330.0 296.0
v 314.0 330.0 456.0
v 472.0 330.0 406.0
f -4 -3 -2 -1
usemtl white
v 423.0 0.0 247.0
v 423.0 330.0 247.0
v 472.0 330.0 406.0
v 472.0 0.0 406.0
f -4 -3 -2 -1
v 472.0 0.0 406.0
v 472.0 330.0 406.0
v 314.0 330.0 456.0
v 314.0 0.0 456.0
f -4 -3 -2 -1
v 314.0 0.0 456.0
v 314.0 330.0 456.0
v 265.0 330.0 296.0
v 265.0 0.0 296.0
f -4 -3 -2 -1
v 265.0 0.0 296.0
v 265.0 330.0 296.0
v 423.0 330.0 247.0
v 423.0 0.0 247.0
f -4 -3 -2 -1

View file

@ -1,2 +1,3 @@
pub mod basic_scene;
pub mod parse_obj;
pub mod triangle_bvh;

View file

@ -0,0 +1,403 @@
use miette::{bail, miette, Context, IntoDiagnostic, LabeledSpan, Result, Severity};
use nom::{
branch::alt,
bytes::complete::{tag, take_till, take_till1},
character,
error::Error,
multi::{fill, many1},
number::complete::double,
Finish, IResult,
};
use ray_tracing_core::prelude::*;
use std::{collections::HashMap, path::Path};
#[derive(Debug)]
pub struct ObjData {
pub vertices: Vec<Pos3>,
pub normals: Vec<Dir3>,
pub texcoord: Vec<[Float; 2]>,
pub faces: Vec<Face>,
pub materials: Vec<ObjMaterial>,
}
#[derive(Debug)]
pub struct ObjMaterial {
name: String,
map: HashMap<String, String>,
}
#[derive(Debug)]
pub struct Face {
pub v: Vec<u32>,
pub t: Option<Vec<u32>>,
pub n: Option<Vec<u32>>,
pub mat: Option<u32>,
}
#[derive(Debug)]
pub struct RelativeFace {
pub v: Vec<i32>,
pub t: Option<Vec<i32>>,
pub n: Option<Vec<i32>>,
}
#[derive(Debug)]
enum Line<'a> {
Vertex(Pos3),
Normal(Dir3),
Texcoord([Float; 2]),
Face(RelativeFace),
Material(&'a str),
Mtllib(&'a str),
Empty,
}
fn remove_whitespace(i: &str) -> IResult<&str, ()> {
let (i, _) = take_till(|c| c != ' ')(i)?;
Ok((i, ()))
}
fn parse_float(i: &str) -> IResult<&str, Float> {
let (i, _) = remove_whitespace(i)?;
double(i).map(|(i, f)| (i, f as Float))
}
fn parse_vertex(i: &str) -> IResult<&str, Line> {
let mut v = [0.0; 3];
let (i, _) = fill(parse_float, &mut v)(i)?;
Ok((i, Line::Vertex(Pos3::new(v[0], v[1], v[2]))))
}
fn parse_normal(i: &str) -> IResult<&str, Line> {
let mut v = [0.0; 3];
let (i, _) = fill(parse_float, &mut v)(i)?;
Ok((i, Line::Normal(Dir3::new(v[0], v[1], v[2]))))
}
fn parse_texcoord(i: &str) -> IResult<&str, Line> {
let mut v = [0.0; 2];
let (i, _) = fill(parse_float, &mut v)(i)?;
Ok((i, Line::Texcoord(v)))
}
fn parse_identifier(i: &str) -> IResult<&str, &str> {
let (i, ()) = remove_whitespace(i)?;
alt((tag("#"), take_till(|c| c == ' ')))(i)
}
fn face_vertex_empty(i: &str) -> IResult<&str, i32> {
let (i, _) = remove_whitespace(i)?;
let (i, v) = character::complete::i32(i)?;
Ok((i, v))
}
fn face_vertex_normal(i: &str) -> IResult<&str, (i32, i32)> {
let (i, _) = remove_whitespace(i)?;
let (i, v) = character::complete::i32(i)?;
let (i, _) = tag("//")(i)?;
let (i, n) = character::complete::i32(i)?;
Ok((i, (v, n)))
}
fn face_vertex_tex(i: &str) -> IResult<&str, (i32, i32)> {
let (i, _) = remove_whitespace(i)?;
let (i, v) = character::complete::i32(i)?;
let (i, _) = tag("/")(i)?;
let (i, t) = character::complete::i32(i)?;
Ok((i, (v, t)))
}
fn face_vertex_normal_tex(i: &str) -> IResult<&str, (i32, i32, i32)> {
let (i, _) = remove_whitespace(i)?;
let (i, v) = character::complete::i32(i)?;
let (i, _) = tag("/")(i)?;
let (i, t) = character::complete::i32(i)?;
let (i, _) = tag("/")(i)?;
let (i, n) = character::complete::i32(i)?;
Ok((i, (v, t, n)))
}
fn parse_face(i: &str) -> IResult<&str, Line> {
if let Ok((i, s)) = many1(face_vertex_normal_tex)(i) {
if s.len() >= 3 {
Ok((
i,
Line::Face(RelativeFace {
v: s.iter().map(|t| t.0).collect(),
t: Some(s.iter().map(|t| t.1).collect()),
n: Some(s.iter().map(|t| t.2).collect()),
}),
))
} else {
Err(nom::Err::Error(Error {
input: i,
code: nom::error::ErrorKind::Tag,
}))
}
} else if let Ok((i, s)) = many1(face_vertex_normal)(i) {
if s.len() >= 3 {
Ok((
i,
Line::Face(RelativeFace {
v: s.iter().map(|t| t.0).collect(),
t: None,
n: Some(s.iter().map(|t| t.1).collect()),
}),
))
} else {
Err(nom::Err::Error(Error {
input: i,
code: nom::error::ErrorKind::Tag,
}))
}
} else if let Ok((i, s)) = many1(face_vertex_tex)(i) {
if s.len() >= 3 {
Ok((
i,
Line::Face(RelativeFace {
v: s.iter().map(|t| t.0).collect(),
t: Some(s.iter().map(|t| t.1).collect()),
n: None,
}),
))
} else {
Err(nom::Err::Error(Error {
input: i,
code: nom::error::ErrorKind::Tag,
}))
}
} else if let Ok((i, s)) = many1(face_vertex_empty)(i) {
if s.len() >= 3 {
Ok((
i,
Line::Face(RelativeFace {
v: s,
t: None,
n: None,
}),
))
} else {
Err(nom::Err::Error(Error {
input: i,
code: nom::error::ErrorKind::Tag,
}))
}
} else {
Err(nom::Err::Error(Error {
input: i,
code: nom::error::ErrorKind::Tag,
}))
}
}
fn parse_mtllib(i: &str) -> IResult<&str, Line> {
let (i, s) = take_till(|c| c == ' ')(i)?;
Ok((i, Line::Mtllib(s)))
}
fn parse_mtl(i: &str) -> IResult<&str, Line> {
let (i, s) = take_till(|c| c == ' ')(i)?;
Ok((i, Line::Material(s)))
}
fn parse_line(i: &str) -> IResult<&str, Line> {
let (i, ident) = parse_identifier(i)?;
let (i, _) = remove_whitespace(i)?;
match ident {
"v" => parse_vertex(i),
"#" | "" => Ok((i, Line::Empty)),
"f" => parse_face(i),
"o" => Ok((i, Line::Empty)),
"vn" => parse_normal(i),
"vt" => parse_texcoord(i),
"usemtl" => parse_mtl(i),
"mtllib" => parse_mtllib(i),
_ => Err(nom::Err::Failure(nom::error::Error {
input: i,
code: nom::error::ErrorKind::Fail,
})),
}
}
fn handle_relative(x: i32, len: i32) -> Result<u32> {
if x == 0 {
Err(miette!("Index cannot be 0"))
} else if x > 0 {
if x <= len {
Ok(x as u32 - 1)
} else {
Err(miette!("Index {x} out of bounds"))
}
} else if len + x >= 0 {
Ok((len + x) as u32)
} else {
Err(miette!("Index {x} out of bounds"))
}
}
enum MaterialsLine<'a> {
Material(&'a str),
KeyValue(&'a str, &'a str),
Empty,
}
fn parse_new_material(i: &str) -> IResult<&str, MaterialsLine> {
let (i, ident) = take_till1(|c| c == ' ')(i)?;
Ok((i, MaterialsLine::Material(ident)))
}
fn parse_line_material(i: &str) -> IResult<&str, MaterialsLine> {
let (i, ident) = parse_identifier(i)?;
let (i, _) = remove_whitespace(i)?;
match ident {
"newmtl" => parse_new_material(i),
"#" | "" => Ok((i, MaterialsLine::Empty)),
_ => Ok((i, MaterialsLine::KeyValue(ident, i))),
}
}
fn read_mtllib(path: impl AsRef<Path>) -> Result<Vec<ObjMaterial>> {
let string = std::fs::read_to_string(path.as_ref())
.into_diagnostic()
.with_context(|| format!("opening file {:?}", path.as_ref()))?;
let mut materials = Vec::new();
let mut current_material = None;
for (i, l) in string.lines().enumerate() {
let t = match parse_line_material(l).finish() {
Ok(t) => t.1,
Err(e) => {
return Err(miette!(
severity = Severity::Error,
code = e.code.description(),
labels = vec![LabeledSpan::at_offset(l.len() - e.input.len(), "here")],
"unable to parse line {}",
i + 1
)
.with_source_code(l.to_string()));
}
};
match t {
MaterialsLine::Material(name) => {
if let Some(m) = current_material.take() {
materials.push(m);
}
current_material = Some(ObjMaterial {
name: name.to_string(),
map: HashMap::new(),
});
}
MaterialsLine::KeyValue(k, v) => {
if let Some(m) = current_material.as_mut() {
m.map.insert(k.to_string(), v.to_string());
} else {
bail!("Material property appears multiple times in line {}", i + 1);
}
}
MaterialsLine::Empty => (),
}
}
if let Some(m) = current_material {
materials.push(m);
}
Ok(materials)
}
impl ObjData {
pub fn new(path: impl AsRef<Path>) -> Result<Self> {
let string = std::fs::read_to_string(path.as_ref())
.into_diagnostic()
.with_context(|| format!("opening file {:?}", path.as_ref()))?;
let mut vertices = Vec::new();
let mut normals = Vec::new();
let mut texcoord = Vec::new();
let mut faces = Vec::new();
let mut materials = Vec::new();
let mut materials_map = HashMap::new();
let mut current_material = None;
for (i, l) in string.lines().enumerate() {
let t = match parse_line(l).finish() {
Ok(t) => t.1,
Err(e) => {
return Err(miette!(
severity = Severity::Error,
code = e.code.description(),
labels = vec![LabeledSpan::at_offset(l.len() - e.input.len(), "here")],
"unable to parse line {}",
i + 1
)
.with_source_code(l.to_string()));
}
};
match t {
Line::Vertex(v) => vertices.push(v),
Line::Normal(n) => normals.push(n),
Line::Texcoord(t) => texcoord.push(t),
Line::Face(f) => faces.push(Face {
v: f.v
.iter()
.map(|&c| {
handle_relative(c, vertices.len() as i32)
.with_context(|| format!("In line {}", i + 1))
})
.collect::<Result<Vec<_>>>()?,
t: f.t
.map(|t| {
t.iter()
.map(|&c| {
handle_relative(c, texcoord.len() as i32)
.with_context(|| format!("In line {}", i + 1))
})
.collect::<Result<Vec<_>>>()
})
.transpose()?,
n: f.n
.map(|t| {
t.iter()
.map(|&c| {
handle_relative(c, normals.len() as i32)
.with_context(|| format!("In line {}", i + 1))
})
.collect::<Result<Vec<_>>>()
})
.transpose()?,
mat: current_material,
}),
Line::Material(s) => {
current_material = Some(
*materials_map
.get(s)
.ok_or(miette!("Unknown material \"{s}\" in line {}", i + 1))?,
);
}
Line::Mtllib(s) => {
for m in read_mtllib(path.as_ref().parent().ok_or(miette!("Unable "))?.join(s))?
{
materials_map.insert(m.name.clone(), materials.len() as u32);
materials.push(m);
}
}
Line::Empty => (),
}
}
Ok(Self {
vertices,
normals,
texcoord,
faces,
materials,
})
}
}