diff --git a/assignment-1/.gitignore b/assignment-1/.gitignore index 8aaef81..58e5cde 100644 --- a/assignment-1/.gitignore +++ b/assignment-1/.gitignore @@ -1,3 +1,5 @@ /target +/assignment-1 *.ppm *.zip +*.pdf diff --git a/assignment-1/Makefile b/assignment-1/Makefile index 49fa46e..79176e2 100644 --- a/assignment-1/Makefile +++ b/assignment-1/Makefile @@ -1,13 +1,18 @@ -.PHONY: clean +.PHONY: all clean + +DOCKER := docker +ZIP := zip +PANDOC := pandoc HANDIN := hw1a.michael.zhang.zip - -BINARY := target/release/assignment-1 +BINARY := assignment-1 +WRITEUP := writeup.pdf +SOURCES := $(shell find -name "*.rs") all: $(HANDIN) -$(BINARY): - docker run \ +$(BINARY): $(SOURCES) + $(DOCKER) run \ --rm \ -v "$(shell pwd)":/usr/src/myapp \ -v cargo-registry:/usr/local/cargo \ @@ -15,9 +20,13 @@ $(BINARY): -w /usr/src/myapp \ rust \ cargo build --release + mv target/release/assignment-1 $@ -$(HANDIN): $(BINARY) Cargo.toml Cargo.lock README.md - zip -r $@ src $< +$(HANDIN): $(BINARY) $(WRITEUP) Cargo.toml Cargo.lock README.md + $(ZIP) -r $@ src $< + +writeup.pdf: writeup.md + $(PANDOC) -o $@ $< clean: - rm -f $(HANDIN) $(BINARY) + rm -f $(HANDIN) $(BINARY) $(WRITEUP) diff --git a/assignment-1/README.md b/assignment-1/README.md index a3f8d2e..e69de29 100644 --- a/assignment-1/README.md +++ b/assignment-1/README.md @@ -1 +0,0 @@ -# Raycaster diff --git a/assignment-1/doc/fov.jpg b/assignment-1/doc/fov.jpg new file mode 100644 index 000000000..e9d18b5 Binary files /dev/null and b/assignment-1/doc/fov.jpg differ diff --git a/assignment-1/doc/map.jpg b/assignment-1/doc/map.jpg new file mode 100644 index 000000000..c61cb7e Binary files /dev/null and b/assignment-1/doc/map.jpg differ diff --git a/assignment-1/doc/rot.jpg b/assignment-1/doc/rot.jpg new file mode 100644 index 000000000..d00ae6d Binary files /dev/null and b/assignment-1/doc/rot.jpg differ diff --git a/assignment-1/src/main.rs b/assignment-1/src/main.rs index ad58d68..b1b536e 100644 --- a/assignment-1/src/main.rs +++ b/assignment-1/src/main.rs @@ -59,7 +59,13 @@ fn main() -> Result<()> { move |px: usize, py: usize| { let x_component = pixel_base_x * px as f64; let y_component = pixel_base_y * py as f64; - view_window.upper_left + x_component + y_component + + // Without adding this, we would be getting the top-left of the pixel's + // rectangle. We want the center, so add half of the pixel size as + // well. + let center_offset = (pixel_base_x + pixel_base_y) / 2.0; + + view_window.upper_left + x_component + y_component + center_offset } }; diff --git a/assignment-1/writeup.md b/assignment-1/writeup.md new file mode 100644 index 000000000..d11db0b --- /dev/null +++ b/assignment-1/writeup.md @@ -0,0 +1,73 @@ +--- +geometry: margin=2cm +output: pdf_document +--- + +# Raycaster + +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 +see where it would intersect objects. + +### Creating a virtual screen + +The virtual screen is determined first using the eye's position and where it's +looking. This gives us a single 3d vector, but it doesn't give us a 2d screen in +the world. This is where the field of view (FOV) comes in; the FOV determines +how many degrees the screen should take up. + +![Field of view](doc/fov.jpg){width=180px} + +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. + +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 +project rays. As the two black rectangles in the diagram above demonstrates, +changing the distance would still allow the viewer to see the same amount of the +scene. (using the word _amount_ very loosely here to mean percentage of the +landscape, rather than # of pixels, which is determined by the actual image +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: + +![Rotation determined by up direction](doc/rot.jpg){width=240px} + +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 +example; simply move the eye position to be incredibly close to the object that +we are observing, and increase the field of view to cover the entire object. + +Because the rays are going in much different directions and travelling different +distances, the corners of the image will seem more stretched than if we were +observing the object from afar and all the rays are in approximately the same +part of the virtual screen. + +One other point to make is that we're currently using a rectangle for our +virtual screen, which automatically does a bit of the distortion. If instead we +were to use a curved lens-like shape, then the rays pointing to any pixel of the +screen would be travelling the same distance. Moving the eye position closer to +the object would still generate distortion, but to a lesser extent. + +### Mapping image pixels + +After the rectangle has been determined, we can simply pick one corner to start +as an anchor, and then find out what pixel values would correspond to it. For +example, in the image below: + +![Mapping image pixels](doc/map.jpg){width=240px} + +I would pick a starting point like $A$, and then take the vector $B-A$ and +subdivide it into 4 pieces, letting $\Delta x = \frac{B-A}{4}$. Then, same thing +for the $y$ direction, I would set $\Delta y = \frac{D-A}{4}$. Taking $A + +x_i \times \Delta x + y_i \times \Delta y$ yields the precise coordinate +location for any pixel. + +(Technically really we would want the middle of the pixel, so just add +$\frac{\Delta x + \Delta y}{2}$ to the point to get that) diff --git a/flake.nix b/flake.nix index 9bbd77b..4ab03ee 100644 --- a/flake.nix +++ b/flake.nix @@ -12,16 +12,22 @@ toolchain = pkgs.fenix.stable; in rec { devShell = pkgs.mkShell { - packages = - (with pkgs; [ cargo-watch cargo-deny cargo-edit zip unzip ]) - ++ (with toolchain; [ - cargo - rustc - clippy + packages = (with pkgs; [ + cargo-watch + cargo-deny + cargo-edit + pandoc + zip + unzip + texlive.combined.scheme-full + ]) ++ (with toolchain; [ + cargo + rustc + clippy - # Get the nightly version of rustfmt so we can wrap comments - pkgs.fenix.default.rustfmt - ]); + # Get the nightly version of rustfmt so we can wrap comments + pkgs.fenix.default.rustfmt + ]); CARGO_UNSTABLE_SPARSE_REGISTRY = "true"; }; });