Textures working a little bit

This commit is contained in:
hal8174 2025-08-16 01:35:52 +02:00
parent 1196cb7758
commit 2476d2d49a
Signed by: hal8174
SSH key fingerprint: SHA256:JwuqS+eVfISfKr+DkDQ6NWAbGd1jFAHkPpCM1yCnlTs
7 changed files with 407 additions and 84 deletions

View file

@ -9,3 +9,4 @@ clap = { version = "4.5.41", features = ["derive"] }
miette = { version = "7.6.0", features = ["fancy"] }
nom = "8.0.0"
thiserror = "2.0.12"
image = "0.25.6"

View file

@ -25,9 +25,9 @@ struct Lexer {
}
impl Lexer {
fn new(path: impl AsRef<Path>) -> Result<Self> {
fn new(path: PathBuf, base_path: PathBuf) -> Result<Self> {
Ok(Self {
input: Tokenizer::new(path)?,
input: Tokenizer::new(path, base_path)?,
})
}
}
@ -396,7 +396,7 @@ impl Lexer {
"Rotate" => Some(parse_rotate(&mut self.input)),
"Transform" => Some(parse_transform(&mut self.input).map(Statement::Transform)),
"Texture" => Some(
texture::parse_texture(&mut self.input)
texture::parse_texture(&mut self.input, textures)
.map(|(name, texture)| Statement::Texture(name, texture)),
),
"ConcatTransform" => {
@ -545,23 +545,25 @@ impl<I: Iterator<Item = Result<u8, std::io::Error>>> Iterator for BytesToChar<I>
}
}
struct Parser<P> {
path: P,
inner: Option<Box<Parser<PathBuf>>>,
struct Parser {
path: PathBuf,
base_path: PathBuf,
inner: Option<Box<Parser>>,
iter: Lexer,
}
impl<P: AsRef<Path> + std::fmt::Debug> Parser<P> {
fn new(path: P) -> Result<Self> {
impl Parser {
fn new(path: PathBuf, base_path: PathBuf) -> Result<Self> {
Ok(Self {
iter: Lexer::new(path.as_ref())?,
iter: Lexer::new(path.clone(), base_path.clone())?,
base_path,
path,
inner: None,
})
}
}
impl<P: AsRef<Path>> Parser<P> {
impl Parser {
fn next(
&mut self,
textures: &HashMap<String, Arc<dyn PbrtTexture>>,
@ -575,8 +577,8 @@ impl<P: AsRef<Path>> Parser<P> {
match self.iter.next(textures) {
Some(Ok(Statement::Include(s))) => {
let path = self.path.as_ref().parent().unwrap().join(s);
self.inner = Some(Box::new(Parser::new(path).unwrap()));
let path = self.path.parent().unwrap().join(s);
self.inner = Some(Box::new(Parser::new(path, self.base_path.clone()).unwrap()));
self.next(textures)
}
@ -670,7 +672,13 @@ fn inner_parse_pbrt(path: impl AsRef<Path> + std::fmt::Debug) -> Result<Pbrt> {
// unwrap on context.last() ok because context is never empty
let mut context_ctm = vec![AffineTransform::identity()];
let mut parser = Parser::new(path)?;
let mut parser = Parser::new(
path.as_ref().to_path_buf(),
path.as_ref()
.parent()
.ok_or_else(|| miette!("parent from file not found"))?
.to_path_buf(),
)?;
// parse global settings
@ -761,10 +769,15 @@ fn inner_parse_pbrt(path: impl AsRef<Path> + std::fmt::Debug) -> Result<Pbrt> {
Statement::Unknown(s, _items) => {
eprintln!("Unknown statement: {s}")
}
Statement::Texture(name, texture) => {
textures.insert(name, texture);
}
s => bail!("unexpected statemnet in world settings: {s:?}"),
}
}
dbg!(textures);
Ok(pbrt)
}

View file

@ -1,19 +1,23 @@
use imagemap::{ImageMapEncoding, ImageMapWrap, SpectrumImageMapTexture};
use miette::{Result, miette};
use ray_tracing_core::{color::Color, prelude::Float};
use std::sync::Arc;
use ray_tracing_core::{affine_transform::AffineTransform, color::Color, prelude::Float};
use std::{collections::HashMap, ops::Deref, sync::Arc};
use crate::tokenizer::Tokenizer;
pub trait PbrtTexture: std::fmt::Debug {
mod checkerboard;
mod imagemap;
pub trait PbrtTexture: std::fmt::Debug + Send + Sync {
fn get_2d_float_texture(self: Arc<Self>) -> Result<Arc<dyn Pbrt_2d_float_texture>>;
fn get_2d_spectrum_texture(self: Arc<Self>) -> Result<Arc<dyn Pbrt_2d_spectrum_texture>>;
}
pub trait Pbrt_2d_float_texture: std::fmt::Debug {
pub trait Pbrt_2d_float_texture: std::fmt::Debug + Sync + Send {
fn get(&self, u: Float, v: Float) -> Float;
}
pub trait Pbrt_2d_spectrum_texture: std::fmt::Debug {
pub trait Pbrt_2d_spectrum_texture: std::fmt::Debug + Sync + Send {
fn get(&self, u: Float, v: Float) -> Color;
}
@ -33,12 +37,20 @@ impl TextureMapping {
}
}
pub fn parse_texture(input: &mut Tokenizer) -> Result<(String, Arc<dyn PbrtTexture>)> {
let texture_name: String = input.parse_next()?;
let texture_type: String = input.parse_next()?;
fn parse_spectrum(input: &mut Tokenizer) -> Result<Color> {
todo!()
}
fn parse_rgb(input: &mut Tokenizer) -> Result<Color> {
let t = input.parse_list()?;
Ok(Color::new(t[0], t[1], t[2]))
}
fn parse_spectrum_texture(
input: &mut Tokenizer,
textures: &HashMap<String, Arc<dyn PbrtTexture>>,
) -> Result<Arc<dyn PbrtTexture>> {
let texture_class: String = input.parse_next()?;
match texture_class.as_str() {
"\"checkerboard\"" => {
let t = parse_dict!(input =>
@ -47,54 +59,115 @@ pub fn parse_texture(input: &mut Tokenizer) -> Result<(String, Arc<dyn PbrtTextu
vscale, Float, 1.0;
udelta, Float, 0.0;
vdelta, Float, 0.0;
dimension, u8, 2
dimension, u8, 2;
tex1, ValueTexture<Color, String>, ValueTexture::Value(Color::white());
tex2, ValueTexture<Color, String>, ValueTexture::Value(Color::black())
=>
mapping, "\"mapping\"", TextureMapping::new(input.parse_parameter()?)?;
uscale, "\"uscale\"", input.parse_parameter()?;
vscale, "\"vscale\"", input.parse_parameter()?;
udelta, "\"udelta\"", input.parse_parameter()?;
vdelta, "\"vdelta\"", input.parse_parameter()?;
dimension, "\"dimension\"", input.parse_parameter()?
mapping, "\"string mapping\"", TextureMapping::new(input.parse_parameter()?)?;
uscale, "\"float uscale\"", input.parse_parameter()?;
vscale, "\"float vscale\"", input.parse_parameter()?;
udelta, "\"float udelta\"", input.parse_parameter()?;
vdelta, "\"float vdelta\"", input.parse_parameter()?;
dimension, "\"integer dimension\"", input.parse_parameter()?;
tex1, "\"rgb tex1\"", ValueTexture::Value(parse_rgb(input)?);
tex1, "\"spectrum tex1\"", ValueTexture::Value(parse_spectrum(input)?);
tex1, "\"texture tex1\"", ValueTexture::Texture(input.parse_parameter()?);
tex2, "\"rgb tex2\"", ValueTexture::Value(parse_rgb(input)?);
tex2, "\"spectrum tex2\"", ValueTexture::Value(parse_spectrum(input)?);
tex2, "\"texture tex2\"", ValueTexture::Texture(input.parse_parameter()?)
);
dbg!(t);
todo!()
let mapping = UVMapping::new(t.mapping, t.uscale, t.vscale, t.udelta, t.vdelta);
match t.dimension {
2 => Ok(Arc::new(checkerboard::SpectrumCheckerboardTexture2d {
mapping,
tex: [
match t.tex1 {
ValueTexture::Value(v) => ValueTexture::Value(v),
ValueTexture::Texture(name) => ValueTexture::Texture(
Arc::clone(
textures
.get(&name)
.ok_or_else(|| miette!("unable to find texture"))?,
)
.get_2d_spectrum_texture()?,
),
},
match t.tex2 {
ValueTexture::Value(v) => ValueTexture::Value(v),
ValueTexture::Texture(name) => ValueTexture::Texture(
Arc::clone(
textures
.get(&name)
.ok_or_else(|| miette!("unable to find texture"))?,
)
.get_2d_spectrum_texture()?,
),
},
],
})),
_ => Err(miette!("Only dimension 2 and 3 are supported")),
}
}
"\"checkerboard\"" => {
"\"imagemap\"" => {
let t = parse_dict!(input =>
mapping, TextureMapping, TextureMapping::UV;
uscale, Float, 1.0;
vscale, Float, 1.0;
udelta, Float, 0.0;
vdelta, Float, 0.0
vdelta, Float, 0.0;
filename, String, String::from("");
wrap, ImageMapWrap, ImageMapWrap::Repeat;
encoding, ImageMapEncoding, ImageMapEncoding::SRGB;
scale, Float, 1.0
=>
mapping, "\"mapping\"", TextureMapping::new(input.parse_parameter()?)?;
uscale, "\"uscale\"", input.parse_parameter()?;
vscale, "\"vscale\"", input.parse_parameter()?;
udelta, "\"udelta\"", input.parse_parameter()?;
vdelta, "\"vdelta\"", input.parse_parameter()?
mapping, "\"string mapping\"", TextureMapping::new(input.parse_parameter()?)?;
uscale, "\"float uscale\"", input.parse_parameter()?;
vscale, "\"float vscale\"", input.parse_parameter()?;
udelta, "\"float udelta\"", input.parse_parameter()?;
vdelta, "\"float vdelta\"", input.parse_parameter()?;
filename, "\"string filename\"", input.parse_parameter()?;
wrap, "\"string wrap\"", ImageMapWrap::new(input.parse_parameter()?)?;
encoding, "\"string encoding\"", ImageMapEncoding::new(input.parse_parameter()?)?;
scale, "\"float scale\"", input.parse_parameter()?
);
todo!()
let mapping = UVMapping::new(t.mapping, t.uscale, t.vscale, t.udelta, t.vdelta);
let path = dbg!(input.get_base_path()).join(t.filename.trim_matches('\"'));
Ok(Arc::new(SpectrumImageMapTexture::new(
mapping,
t.scale,
t.wrap,
t.encoding,
false,
dbg!(path),
)?))
}
_ => Err(miette!("unknown error")),
_ => Err(miette!("unknown error {texture_class}")),
}
}
pub fn parse_texture(
input: &mut Tokenizer,
textures: &HashMap<String, Arc<dyn PbrtTexture>>,
) -> Result<(String, Arc<dyn PbrtTexture>)> {
let texture_name: String = input.parse_next()?;
let texture_type: String = input.parse_next()?;
match texture_type.as_str() {
"\"spectrum\"" => parse_spectrum_texture(input, textures).map(|t| (texture_name, t)),
_ => Err(miette!("Texture type has to be spectrum or float")),
}
}
#[derive(Debug)]
enum FloatTexture2d {
Value(Float),
Texture {
float: Option<Arc<dyn Pbrt_2d_float_texture>>,
spectrum: Option<Arc<dyn Pbrt_2d_spectrum_texture>>,
},
}
#[derive(Debug)]
struct TextureD2<T> {
inner: T,
struct UVMapping {
uscale: Float,
vscale: Float,
udelta: Float,
@ -102,19 +175,32 @@ struct TextureD2<T> {
mapping: TextureMapping,
}
impl<T: PbrtTexture> PbrtTexture for TextureD2<T> {
fn get_2d_float_texture(self: Arc<Self>) -> Result<Arc<dyn Pbrt_2d_float_texture>> {
todo!()
impl UVMapping {
fn new(
mapping: TextureMapping,
uscale: Float,
vscale: Float,
udelta: Float,
vdelta: Float,
) -> Self {
Self {
uscale,
vscale,
udelta,
vdelta,
mapping,
}
}
fn get_2d_spectrum_texture(self: Arc<Self>) -> Result<Arc<dyn Pbrt_2d_spectrum_texture>> {
todo!()
fn map(&self, u: Float, v: Float) -> (Float, Float) {
match self.mapping {
TextureMapping::UV => (u * self.uscale + self.udelta, v * self.vscale + self.vdelta),
TextureMapping::Spherical => todo!(),
}
}
}
#[derive(Debug)]
struct CheckerboardTexture {
dimension: u8,
tex1: FloatTexture2d,
tex2: FloatTexture2d,
enum ValueTexture<V, T> {
Value(V),
Texture(T),
}

View file

@ -0,0 +1,74 @@
use super::*;
#[derive(Debug)]
pub(super) struct FloatCheckerboardTexture2d {
pub(super) mapping: UVMapping,
pub(super) tex: [ValueTexture<Float, Arc<dyn Pbrt_2d_float_texture>>; 2],
}
impl Pbrt_2d_float_texture for FloatCheckerboardTexture2d {
fn get(&self, u: Float, v: Float) -> Float {
let (u, v) = self.mapping.map(u, v);
let even = if u >= 0.0 {
u.fract() >= 0.5
} else {
u.fract() >= -0.5
} ^ if v >= 0.0 {
v.fract() >= 0.5
} else {
v.fract() >= -0.5
};
match &self.tex[even as usize] {
ValueTexture::Value(v) => *v,
ValueTexture::Texture(t) => t.get(u, v),
}
}
}
impl PbrtTexture for FloatCheckerboardTexture2d {
fn get_2d_float_texture(self: Arc<Self>) -> Result<Arc<dyn Pbrt_2d_float_texture>> {
Ok(self)
}
fn get_2d_spectrum_texture(self: Arc<Self>) -> Result<Arc<dyn Pbrt_2d_spectrum_texture>> {
Err(miette!("Unable to use this texture as a spectrum texture"))
}
}
#[derive(Debug)]
pub(super) struct SpectrumCheckerboardTexture2d {
pub(super) mapping: UVMapping,
pub(super) tex: [ValueTexture<Color, Arc<dyn Pbrt_2d_spectrum_texture>>; 2],
}
impl Pbrt_2d_spectrum_texture for SpectrumCheckerboardTexture2d {
fn get(&self, u: Float, v: Float) -> Color {
let (u, v) = self.mapping.map(u, v);
let even = if u >= 0.0 {
u.fract() >= 0.5
} else {
u.fract() >= -0.5
} ^ if v >= 0.0 {
v.fract() >= 0.5
} else {
v.fract() >= -0.5
};
match &self.tex[even as usize] {
ValueTexture::Value(v) => *v,
ValueTexture::Texture(t) => t.get(u, v),
}
}
}
impl PbrtTexture for SpectrumCheckerboardTexture2d {
fn get_2d_float_texture(self: Arc<Self>) -> Result<Arc<dyn Pbrt_2d_float_texture>> {
Err(miette!("Unable to use this texture as a float texture"))
}
fn get_2d_spectrum_texture(self: Arc<Self>) -> Result<Arc<dyn Pbrt_2d_spectrum_texture>> {
Ok(self)
}
}

View file

@ -0,0 +1,128 @@
use std::path::Path;
use miette::IntoDiagnostic;
use super::*;
#[derive(Debug)]
pub(super) enum ImageMapWrap {
Repeat,
Black,
Clamp,
}
impl ImageMapWrap {
pub(super) fn new(x: String) -> Result<Self> {
match x.as_str() {
"\"repeat\"" => Ok(Self::Repeat),
"\"black\"" => Ok(Self::Black),
"\"clamp\"" => Ok(Self::Clamp),
_ => Err(miette!("error image map wrap")),
}
}
}
#[derive(Debug)]
pub(super) enum ImageMapEncoding {
SRGB,
Linear,
Gamma(Float),
}
impl ImageMapEncoding {
pub(super) fn new(x: String) -> Result<Self> {
match x.as_str() {
"\"sRGB\"" => Ok(Self::SRGB),
"\"linear\"" => Ok(Self::Linear),
s if s.starts_with("\"gamma ") => Ok(Self::Gamma(
s.split_at(7)
.1
.trim_matches('\"')
.parse()
.into_diagnostic()?,
)),
_ => Err(miette!("error image map encoding")),
}
}
}
#[derive(Debug)]
pub(super) struct SpectrumImageMapTexture {
mapping: UVMapping,
scale: Float,
wrap: ImageMapWrap,
invert: bool,
image: image::Rgb32FImage,
}
fn srgb_nonlinear(x: f32) -> f32 {
if x <= 0.04045 {
x / 12.92
} else {
((x + 0.055) / 1.055).powf(2.4)
}
}
impl SpectrumImageMapTexture {
pub(super) fn new(
mapping: UVMapping,
scale: Float,
wrap: ImageMapWrap,
encoding: ImageMapEncoding,
invert: bool,
path: impl AsRef<Path>,
) -> Result<Self> {
let image = image::open(path).unwrap();
let mut image = image.into_rgb32f();
match encoding {
ImageMapEncoding::SRGB => {
for pixel in image.pixels_mut() {
pixel.0 = [
srgb_nonlinear(pixel.0[0]),
srgb_nonlinear(pixel.0[1]),
srgb_nonlinear(pixel.0[2]),
];
}
}
ImageMapEncoding::Linear => (),
ImageMapEncoding::Gamma(gamma) => {
for pixel in image.pixels_mut() {
pixel.0 = [
pixel.0[0].powf(gamma),
pixel.0[1].powf(gamma),
pixel.0[2].powf(gamma),
];
}
}
}
Ok(Self {
mapping,
scale,
invert,
wrap,
image,
})
}
}
impl Pbrt_2d_spectrum_texture for SpectrumImageMapTexture {
fn get(&self, u: Float, v: Float) -> Color {
let (u, v) = self.mapping.map(u, v);
let (w, h) = self.image.dimensions();
todo!()
}
}
impl PbrtTexture for SpectrumImageMapTexture {
fn get_2d_float_texture(self: Arc<Self>) -> Result<Arc<dyn Pbrt_2d_float_texture>> {
Err(miette!("Unable to use this texture as a float texture"))
}
fn get_2d_spectrum_texture(self: Arc<Self>) -> Result<Arc<dyn Pbrt_2d_spectrum_texture>> {
Ok(self)
}
}

View file

@ -12,19 +12,29 @@ use crate::{BytesToChar, SourceFile};
pub struct Tokenizer {
inner: InnerTokenizer,
path: PathBuf,
base_path: PathBuf,
peeked: Option<Option<Result<(String, SourceSpan)>>>,
last_span: SourceSpan,
}
impl Tokenizer {
pub fn new(path: impl AsRef<Path>) -> Result<Self> {
pub fn new(path: PathBuf, base_path: PathBuf) -> Result<Self> {
Ok(Self {
peeked: None,
path: PathBuf::from(path.as_ref()),
inner: InnerTokenizer::new(path)?,
inner: InnerTokenizer::new(&path)?,
path,
base_path,
last_span: SourceSpan::new(0.into(), 0),
})
}
pub fn get_path(&self) -> &Path {
&self.path
}
pub fn get_base_path(&self) -> &Path {
&self.base_path
}
}
struct InnerTokenizer {