Draw earth texture to the sphere

This commit is contained in:
Michael Zhang 2023-02-25 16:20:59 -06:00
parent 8ab0a9d139
commit bd06d32b34
8 changed files with 85 additions and 19 deletions

View file

@ -29,7 +29,9 @@ pub struct Image {
impl Image {
pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
let file = File::open(path.as_ref())?;
let path = path.as_ref();
let file = File::open(path)
.with_context(|| format!("Could not open file at {path:?}"))?;
Self::read(file)
}

View file

@ -113,7 +113,7 @@ fn main() -> Result<()> {
Ok(match earliest_intersection {
// Take the object's material color
Some((obj_idx, intersection_context, object)) => {
scene.compute_pixel_color(obj_idx, object, intersection_context)
scene.compute_pixel_color(obj_idx, object, intersection_context)?
}
// There was no intersection, so this should default to the scene's

View file

@ -1,5 +1,6 @@
use std::iter;
use anyhow::Result;
use ordered_float::NotNan;
use rand::Rng;
use rayon::prelude::{
@ -10,7 +11,8 @@ use rayon::prelude::{
use crate::{image::Color, ray::Ray, utils::dot, Point, Vector};
use super::{
data::{DepthCueing, Light, LightKind, Object},
data::{DepthCueing, Light, LightKind},
object::Object,
Scene,
};
@ -27,20 +29,22 @@ impl Scene {
obj_idx: usize,
object: &Object,
intersection_context: IntersectionContext,
) -> Color {
// TODO: Does it make sense to make this function fallible from an API
// design standpoint?
) -> Result<Color> {
let material = match self.materials.get(object.material_idx) {
Some(v) => v,
None => return self.bkg_color,
None => bail!("Material index not found."),
};
let diffuse_color = match object.texture_idx {
Some(texture_idx) => {
let texture = &self.textures[texture_idx];
let (u, v) = object.kind.get_texture_coord(&intersection_context)?;
// TODO: need to implement
material.diffuse_color
let texture = match self.textures.get(texture_idx) {
Some(v) => v,
None => bail!("Texture index not found."),
};
texture.pixel_at(u, v)
}
None => material.diffuse_color,
};
@ -132,7 +136,7 @@ impl Scene {
// Need to clamp the result so none of the components goes over 1
let clamped_result = color.map(|v| v.min(1.0));
clamped_result
Ok(clamped_result)
}
/// Perform another ray casting to see if there are any objects obstructing

View file

@ -10,15 +10,17 @@ use crate::{
image::{Color, Image},
scene::{
cylinder::Cylinder,
data::{Attenuation, Light, LightKind, Material, Object},
data::{Attenuation, Light, LightKind, Material},
object::{Object, ObjectKind},
sphere::Sphere,
texture::Texture,
triangle::Triangle,
Scene, texture::Texture,
Scene,
},
Point, Vector,
};
use super::data::{DepthCueing, ObjectKind};
use super::data::DepthCueing;
impl Scene {
/// Parse the input file into a scene

View file

@ -2,15 +2,16 @@ pub mod cylinder;
pub mod data;
pub mod illumination;
pub mod input_file;
pub mod object;
pub mod sphere;
pub mod texture;
pub mod triangle;
pub mod object;
use crate::image::{Color, Image};
use crate::{Point, Point2, Vector};
use self::data::{Attenuation, DepthCueing, Light, Material, Object};
use self::data::{Attenuation, DepthCueing, Light, Material};
use self::object::Object;
use self::texture::Texture;
#[derive(Debug, Default)]

View file

@ -44,4 +44,17 @@ impl ObjectKind {
ObjectKind::Triangle(triangle) => triangle.intersects_ray_at(scene, ray),
}
}
/// Get the coordinates in the texture (between 0 and 1) that corresponds to
/// the intersection point
pub fn get_texture_coord(
&self,
ctx: &IntersectionContext,
) -> Result<(f64, f64)> {
match self {
ObjectKind::Sphere(sphere) => sphere.get_texture_coord(ctx),
ObjectKind::Cylinder(cylinder) => todo!(),
ObjectKind::Triangle(triangle) => todo!(),
}
}
}

View file

@ -1,3 +1,5 @@
use std::f64::consts::PI;
use anyhow::Result;
use ordered_float::NotNan;
@ -72,4 +74,21 @@ impl Sphere {
normal,
}))
}
pub fn get_texture_coord(
&self,
ctx: &IntersectionContext,
) -> Result<(f64, f64)> {
// Reverse engineer the angles from the coordinate of the intersection
let cosp = (ctx.point.z - self.center.z) / self.radius;
let phi = cosp.acos();
let theta =
(ctx.point.y - self.center.y).atan2(ctx.point.x - self.center.x);
// Map theta and phi into 0 - 1 range
let v = phi / PI;
let u = 0.5 + theta / (2.0 * PI);
Ok((u, v))
}
}

View file

@ -8,9 +8,34 @@ impl Texture {
Self(image)
}
pub fn pixel_at_exact(&self, x: usize, y: usize) -> Color {
// TODO: Debug asserts?
self.0.data[y * self.0.width + x]
}
/// Returns a pixel at the given coordinate. For non-lattice coordinates,
/// interpolation of the image is done.
pub fn pixel_at(&self, x: f64, y: f64) -> Option<Color> {
todo!()
/// bi-linear interpolation of the image is done.
pub fn pixel_at(&self, u: f64, v: f64) -> Color {
debug_assert!(0.0 <= u && u <= 1.0, "u must be between 0 and 1");
debug_assert!(0.0 <= v && v <= 1.0, "u must be between 0 and 1");
// Slide 121
let x = u * (self.0.width - 1) as f64;
let y = v * (self.0.height - 1) as f64;
let i = x.floor();
let j = y.floor();
let alpha = x - i;
let beta = y - j;
let i = i as usize;
let j = j as usize;
(1.0 - alpha) * (1.0 - beta) * self.pixel_at_exact(i, j)
+ (alpha) * (1.0 - beta) * self.pixel_at_exact(i + 1, j)
+ (1.0 - alpha) * (beta) * self.pixel_at_exact(i, j + 1)
+ (alpha) * (beta) * self.pixel_at_exact(i + 1, j + 1)
}
}