diff --git a/assignment-1/Makefile b/assignment-1/Makefile index 60ad618..1b2eb43 100644 --- a/assignment-1/Makefile +++ b/assignment-1/Makefile @@ -36,7 +36,7 @@ examples/%.ppm: examples/%.txt examples/%.png: examples/%.ppm convert $< $@ -writeup.pdf: writeup.md +writeup.pdf: writeup.md $(EXAMPLES_PNG) $(PANDOC) -o $@ $< clean: diff --git a/assignment-1/examples/fov-demo-1.txt b/assignment-1/examples/fov-demo-1.txt new file mode 100644 index 000000000..0dd8545 --- /dev/null +++ b/assignment-1/examples/fov-demo-1.txt @@ -0,0 +1,21 @@ +imsize 640 480 +eye 0 0 15 +viewdir 0 0 -1 +hfov 60 +updir 0 1 0 +bkgcolor 0.1 0.1 0.1 + +mtlcolor 0 0.5 0.5 +sphere -1 -2 -5 2 +sphere 3 -5 -1 0.5 + +mtlcolor 0.5 0.5 1 +sphere 1 2 -3 3 +sphere -6 3 -4 1 + +mtlcolor 0.5 0 0.5 +sphere 5 5 -1 1 +sphere -6 -4 -8 7 + +mtlcolor 0.5 1 0.5 +cylinder 5 1 -2 1 -2 1 1 2 diff --git a/assignment-1/examples/fov-demo-2.txt b/assignment-1/examples/fov-demo-2.txt new file mode 100644 index 000000000..0b9ed86 --- /dev/null +++ b/assignment-1/examples/fov-demo-2.txt @@ -0,0 +1,21 @@ +imsize 640 480 +eye 0 0 15 +viewdir 0 0 -1 +hfov 30 +updir 0 1 0 +bkgcolor 0.1 0.1 0.1 + +mtlcolor 0 0.5 0.5 +sphere -1 -2 -5 2 +sphere 3 -5 -1 0.5 + +mtlcolor 0.5 0.5 1 +sphere 1 2 -3 3 +sphere -6 3 -4 1 + +mtlcolor 0.5 0 0.5 +sphere 5 5 -1 1 +sphere -6 -4 -8 7 + +mtlcolor 0.5 1 0.5 +cylinder 5 1 -2 1 -2 1 1 2 diff --git a/assignment-1/examples/up-dir-demo-1.txt b/assignment-1/examples/up-dir-demo-1.txt new file mode 100644 index 000000000..2b1755f --- /dev/null +++ b/assignment-1/examples/up-dir-demo-1.txt @@ -0,0 +1,9 @@ +imsize 640 480 +eye 0 0 8 +viewdir 0 0 -1 +hfov 60 +updir 0 1 0 +bkgcolor 1 1 1 + +mtlcolor 0.5 1 0.5 +cylinder 0 0 -4 1 0 0 1 8 diff --git a/assignment-1/examples/up-dir-demo-2.txt b/assignment-1/examples/up-dir-demo-2.txt new file mode 100644 index 000000000..287f79f --- /dev/null +++ b/assignment-1/examples/up-dir-demo-2.txt @@ -0,0 +1,9 @@ +imsize 640 480 +eye 0 0 8 +viewdir 0 0 -1 +hfov 60 +updir 1 1 0 +bkgcolor 1 1 1 + +mtlcolor 0.5 1 0.5 +cylinder 0 0 -4 1 0 0 1 8 diff --git a/assignment-1/src/scene/cylinder.rs b/assignment-1/src/scene/cylinder.rs index 7d388b2..87b552a 100644 --- a/assignment-1/src/scene/cylinder.rs +++ b/assignment-1/src/scene/cylinder.rs @@ -57,7 +57,7 @@ impl ObjectKind for Cylinder { let discriminant = b * b - 4.0 * a * c; - let mut solutions = match discriminant { + let possible_side_solutions = match discriminant { // Discriminant < 0, means the equation has no solutions. d if d < 0.0 => vec![], @@ -76,6 +76,16 @@ impl ObjectKind for Cylinder { _ => unreachable!("Invalid determinant value: {discriminant}"), }; + // Filter out solutions that don't have a valid Z position. + let side_solutions = possible_side_solutions.into_iter().filter(|t| { + let ray_point = ray.eval(*t); + let rotated_ray_point = rotation_matrix * ray_point; + let z = rotated_ray_point.z - rotated_cylinder_center.z; + + // Check to see if z is between -len/2 and len/2 + z.abs() < self.length / 2.0 + }); + // We also need to add solutions for the two ends of the cylinder, which // uses a similar method except backwards: check intersection points // with the correct z-plane and then see if the points are within the @@ -96,26 +106,19 @@ impl ObjectKind for Cylinder { (o.z + c.z - self.length / 2.0) / r.z, ] }; + // Filter out all the solutions where the z does not lie in the circle - solutions.extend(possible_z_intersections.into_iter().filter(|t| { + let end_solutions = possible_z_intersections.into_iter().filter(|t| { let ray_point = ray.eval(*t); ray_point.x.powi(2) + ray_point.y.powi(2) <= self.radius.powi(2) - })); + }); - // Filter out solutions that don't have a valid Z position. - let solutions = solutions - .into_iter() - .filter(|t| { - let ray_point = ray.eval(*t); - let rotated_ray_point = rotation_matrix * ray_point; - let z = rotated_ray_point.z - rotated_cylinder_center.z; - - // Check to see if z is between -len/2 and len/2 - z.abs() < self.length / 2.0 - }) - .filter_map(|t| NotNan::new(t).ok()); + let solutions = side_solutions.into_iter().chain(end_solutions.into_iter()); // Return the minimum solution - solutions.min().map(|t| t.into_inner()) + solutions + .filter_map(|t| NotNan::new(t).ok()) + .min() + .map(|t| t.into_inner()) } } diff --git a/assignment-1/writeup.md b/assignment-1/writeup.md index f9edae1..5f7dadd 100644 --- a/assignment-1/writeup.md +++ b/assignment-1/writeup.md @@ -5,6 +5,10 @@ output: pdf_document # Raycaster +#### Michael Zhang \ + +--- + Determining the viewing window for the raycaster for this assignment involved creating a "virtual" screen in world coordinates, mapping image pixels into that virtual screen, and then casting a ray through each pixel's world coordinate to @@ -22,7 +26,15 @@ how many degrees the screen should take up. Changing the angle of the field of view would result in a wider or narrower screen, which when paired with the aspect ratio (width / height), would produce a bigger or smaller viewing screen, like the orange box in the above diagram -shows. Simply put, FOV affects how _much_ of the frame you're able to see. +shows. Simply put, FOV affects how _much_ of the frame you're able to see. An +example is shown here: + +![](examples/fov-demo-1.png){width=180px}\ ![](examples/fov-demo-2.png){width=180px} + +The left image uses an FOV of 60, while the right image uses an FOV of 30. As +you can see, the left side has a wider range of vision, which allows it to see +more of the world. (both images can be found in the `examples` directory of the +handin zip) Curiously, distance from the eye actually doesn't really affect the viewing screen very much. The reason is the screen is only used to determine how to @@ -34,10 +46,16 @@ dimensions) The up-direction vector controls the rotation of the scene. Without the up-direction, it would not be possible to tell which rotation the screen should -be in: +be in. ![Rotation determined by up direction](doc/rot.jpg){width=240px} +To see what this looks like, consider the following images, where the left side +uses an up direction of $(0, 1, 0)$, while the right side uses $(1, 1, 0)$ (both +images can be found in the `examples` directory of the handin zip) + +![](examples/up-dir-demo-1.png){width=180px}\ ![](examples/up-dir-demo-2.png){width=180px} + Together, all of these parameters can uniquely determine a virtual screen location, that we can use to cast rays through and fill pixels. We can change any of these to produce an image with a more exaggerated view of the scene for @@ -76,7 +94,8 @@ $\frac{\Delta x + \Delta y}{2}$ to the point to get that) Because of the way I implemented parallel projection, it's recommended to either put the eye much farther back, or use `--distance` to force a much bigger -distance from the eye for the raycaster. This is due to the size of the image. +distance from the eye for the raycaster. See the `--help` to see how this option +is used. ### Cylinder Intersection Notes @@ -84,9 +103,30 @@ First, we will transform the current point into the vector space of the cylinder, so that the cylinder location is $(0, 0, 0)$ and the direction vector is normalized into $(0, 0, 1)$. -Then it's a matter of determining if the $x$ and $y$ coordinates fall into the -space constrained by the equation $(ox + t\times rx - cx)^2 + (oy + t\times ty - -cy)^2 = r^2$ and if $z \le L$. +This can be done by using a rotation matrix (since we are sure this +transformation is just a rotation). This rotation is actually a 2D rotation, +around the normal between the cylinder direction and $(0, 0, 1)$. We then +rotate everything we are working with (the cylinder and the ray) into this +coordinate system to make calculations easier. -See the comments in the code for a more detailed explanation of the equation -used. +Then it's a matter of determining if the $x$ and $y$ coordinates fall into the +space constrained by the equation $(o_x + t\times r_x - c_x)^2 + (o_y + t\times +r_y - c_y)^2 = r^2$ and if $z \le L$. I can solve this using the quadratic +formula the same way as the sphere case. + +We want a quadratic equation of the form $At^2 + Bt + C = 0$. The values for +$A$, $B$, and $C$ are: + +- $A = r_x^2 + r_y^2$ +- $B = 2(r_x(o_x - c_x) + r_y(o_y - c_y))$ +- $C = (c_x - o_x)^2 + (c_y - o_y)^2 - r^2$ + +Solving this for $t$ yields 0-2 solutions depending on if the equation was +satisfied or not. Then, we can plug any solutions we get back into the ray +equation and determine if the $z$-coordinate is in the range of the cylinder +that we want. + +We will also have to do this for the ends of the cylinder, but just backwards. +So we would start with the $z$-coordinate, solve for $t$s where the ray hits +that $z$-plane, and then check the $x$ and $y$ values to see if they satisfy the +ray equation as well.