polish
This commit is contained in:
parent
8a68e44e3d
commit
38f5d4446e
10 changed files with 169 additions and 22 deletions
1
assignment-0/.gitignore
vendored
1
assignment-0/.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
/target
|
/target
|
||||||
|
input
|
||||||
out.ppm
|
out.ppm
|
||||||
|
|
||||||
|
|
40
assignment-0/Cargo.lock
generated
40
assignment-0/Cargo.lock
generated
|
@ -2,6 +2,15 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "0.7.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.68"
|
version = "1.0.68"
|
||||||
|
@ -14,7 +23,9 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
"lazy_static",
|
||||||
"rand",
|
"rand",
|
||||||
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -141,6 +152,12 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.139"
|
version = "0.2.139"
|
||||||
|
@ -153,6 +170,12 @@ version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
|
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.17.0"
|
version = "1.17.0"
|
||||||
|
@ -243,6 +266,23 @@ dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.36.7"
|
version = "0.36.7"
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
[package]
|
[package]
|
||||||
|
authors = ["Michael Zhang <zhan4854@umn.edu>"]
|
||||||
name = "assignment-0"
|
name = "assignment-0"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.68"
|
anyhow = "1.0.68"
|
||||||
clap = { version = "4.1.1", features = ["derive"] }
|
clap = { version = "4.1.1", features = ["derive", "cargo"] }
|
||||||
|
lazy_static = "1.4.0"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
regex = "1.7.1"
|
||||||
|
|
31
assignment-0/README.md
Normal file
31
assignment-0/README.md
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
Assignment 0: Generating turbulence with Rust!
|
||||||
|
===
|
||||||
|
|
||||||
|
This program generates PPM images.
|
||||||
|
|
||||||
|
Using the program
|
||||||
|
---
|
||||||
|
|
||||||
|
The binary provided has been built for Ubuntu, and has been tested on CSELabs.
|
||||||
|
Run `assignment-0 --help` to find out about all the options for using this
|
||||||
|
program.
|
||||||
|
|
||||||
|
Compiling the source
|
||||||
|
---
|
||||||
|
|
||||||
|
This project is a standard Rust program, and can be built with just a Rust
|
||||||
|
compiler. The version of Rust used is the current stable at the time of writing,
|
||||||
|
1.66.
|
||||||
|
|
||||||
|
Build the program using:
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
The binary can be found in the `target` directory.
|
||||||
|
|
||||||
|
Contact
|
||||||
|
---
|
||||||
|
|
||||||
|
Author: Michael Zhang
|
|
@ -1,14 +1,22 @@
|
||||||
use std::io::{Result, Write};
|
use std::io::{Result, Write};
|
||||||
|
|
||||||
|
/// A 24-bit pixel represented by a red, green, and blue value.
|
||||||
pub struct Pixel(pub u8, pub u8, pub u8);
|
pub struct Pixel(pub u8, pub u8, pub u8);
|
||||||
|
|
||||||
pub struct Ppm {
|
/// A representation of an image
|
||||||
|
pub struct Image {
|
||||||
|
/// Width in pixels
|
||||||
pub(crate) width: usize,
|
pub(crate) width: usize,
|
||||||
|
|
||||||
|
/// Height in pixels
|
||||||
pub(crate) height: usize,
|
pub(crate) height: usize,
|
||||||
|
|
||||||
|
/// Pixel data in row-major form.
|
||||||
pub(crate) data: Vec<Pixel>,
|
pub(crate) data: Vec<Pixel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ppm {
|
impl Image {
|
||||||
|
/// Write the image in PPM format to a file.
|
||||||
pub fn write(&self, mut w: impl Write) -> Result<()> {
|
pub fn write(&self, mut w: impl Write) -> Result<()> {
|
||||||
// Header
|
// Header
|
||||||
let header = format!("P3 {} {} 255\n", self.width, self.height);
|
let header = format!("P3 {} {} 255\n", self.width, self.height);
|
|
@ -1,48 +1,111 @@
|
||||||
use std::{fs::File, path::PathBuf};
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::Read,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{bail, Context, Result};
|
||||||
use clap::{Parser, ValueEnum};
|
use clap::{Parser, ValueEnum};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use regex::Regex;
|
||||||
use turbulence::generate_turbulence;
|
use turbulence::generate_turbulence;
|
||||||
|
|
||||||
use crate::value_noise::generate_noise;
|
use crate::value_noise::generate_noise;
|
||||||
|
|
||||||
mod ppm;
|
mod image;
|
||||||
mod turbulence;
|
mod turbulence;
|
||||||
mod value_noise;
|
mod value_noise;
|
||||||
mod vec2;
|
mod vec2;
|
||||||
|
|
||||||
|
/// Noise image generator.
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
|
#[clap(author, version, about, long_about = None)]
|
||||||
struct Opt {
|
struct Opt {
|
||||||
|
/// Path to the input file to use.
|
||||||
|
///
|
||||||
|
/// The input file should follow this format:
|
||||||
|
///
|
||||||
|
/// imsize [width] [height]
|
||||||
|
///
|
||||||
|
/// Where `imsize' is a keyword, and `width' and `height' are integer values
|
||||||
|
/// denoting the desired size of the image to be generated.
|
||||||
|
#[clap()]
|
||||||
|
input_path: PathBuf,
|
||||||
|
|
||||||
|
/// The algorithm to use for pattern generation.
|
||||||
#[clap(long = "algorithm", default_value = "turbulence")]
|
#[clap(long = "algorithm", default_value = "turbulence")]
|
||||||
algorithm: Algorithm,
|
algorithm: Algorithm,
|
||||||
|
|
||||||
#[clap(long = "size", default_value = "1024")]
|
/// The output image path.
|
||||||
size: usize,
|
|
||||||
|
|
||||||
#[clap(short = 'o', long = "out", default_value = "out.ppm")]
|
#[clap(short = 'o', long = "out", default_value = "out.ppm")]
|
||||||
output_path: PathBuf,
|
output_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(ValueEnum, Clone)]
|
#[derive(ValueEnum, Clone)]
|
||||||
enum Algorithm {
|
enum Algorithm {
|
||||||
|
/// Generates noise, which is linearly interpolated randomness.
|
||||||
Noise,
|
Noise,
|
||||||
|
|
||||||
|
/// Fractal sum made with signed noise, used to resemble smoke.
|
||||||
Turbulence,
|
Turbulence,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let opt = Opt::parse();
|
let opt = Opt::parse();
|
||||||
|
|
||||||
|
let (width, height) = parse_input_file(&opt.input_path)?;
|
||||||
|
|
||||||
|
// We will use the default thread-local rng. Future work could include
|
||||||
|
// allowing the rng to be seedable from the command line
|
||||||
let rng = rand::thread_rng();
|
let rng = rand::thread_rng();
|
||||||
|
|
||||||
|
// Pick the algorithm to use based on the option passed via command line
|
||||||
let ppm = match opt.algorithm {
|
let ppm = match opt.algorithm {
|
||||||
Algorithm::Noise => generate_noise(opt.size, opt.size, rng),
|
Algorithm::Noise => generate_noise(width, height, rng),
|
||||||
Algorithm::Turbulence => generate_turbulence(opt.size, opt.size, rng),
|
Algorithm::Turbulence => generate_turbulence(width, height, rng),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Write the PPM image to the output file specified
|
||||||
{
|
{
|
||||||
let file = File::create(opt.output_path)?;
|
let file = File::create(&opt.output_path).with_context(|| {
|
||||||
ppm.write(file)?;
|
format!("Could not create file {:?}.", opt.output_path)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
ppm.write(file).with_context(|| {
|
||||||
|
format!("Could not write to file {:?}.", opt.output_path)
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref INPUT_PATTERN: Regex =
|
||||||
|
Regex::new(r"imsize\s+(?P<width>\d+)\s+(?P<height>\d+)")
|
||||||
|
.expect("Failed to compile regex.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse the input file, returning the target image dimensions
|
||||||
|
fn parse_input_file(path: impl AsRef<Path>) -> Result<(usize, usize)> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
|
||||||
|
// Read the contents from the file
|
||||||
|
let contents = {
|
||||||
|
let mut contents = String::new();
|
||||||
|
let mut file = File::open(path)?;
|
||||||
|
file.read_to_string(&mut contents)?;
|
||||||
|
contents
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse the contents using regex
|
||||||
|
let matches = match INPUT_PATTERN.captures(&contents) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => bail!("Invalid input file syntax."),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract the parsed captures and convert to int
|
||||||
|
let width = matches.name("width").unwrap().as_str().parse()?;
|
||||||
|
let height = matches.name("height").unwrap().as_str().parse()?;
|
||||||
|
|
||||||
|
Ok((width, height))
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
// Follows this algorithm:
|
||||||
// https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/procedural-patterns-noise-part-1/simple-pattern-examples.html
|
// https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/procedural-patterns-noise-part-1/simple-pattern-examples.html
|
||||||
|
|
||||||
use rand::RngCore;
|
use rand::RngCore;
|
||||||
|
|
||||||
use crate::ppm::{Pixel, Ppm};
|
use crate::image::{Image, Pixel};
|
||||||
use crate::value_noise::ValueNoise;
|
use crate::value_noise::ValueNoise;
|
||||||
use crate::vec2::Vec2;
|
use crate::vec2::Vec2;
|
||||||
|
|
||||||
|
@ -10,7 +11,7 @@ pub fn generate_turbulence(
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
rng: impl RngCore,
|
rng: impl RngCore,
|
||||||
) -> Ppm {
|
) -> Image {
|
||||||
let mut noise_map = vec![0.0; width * height];
|
let mut noise_map = vec![0.0; width * height];
|
||||||
let noise = ValueNoise::new(rng);
|
let noise = ValueNoise::new(rng);
|
||||||
|
|
||||||
|
@ -43,7 +44,7 @@ pub fn generate_turbulence(
|
||||||
Pixel(v, v, v)
|
Pixel(v, v, v)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
Ppm {
|
Image {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
data,
|
data,
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
|
// Follows this algorithm:
|
||||||
// https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/procedural-patterns-noise-part-1/creating-simple-2D-noise.html
|
// https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/procedural-patterns-noise-part-1/creating-simple-2D-noise.html
|
||||||
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
use rand::{Rng, RngCore};
|
use rand::{Rng, RngCore};
|
||||||
|
|
||||||
use crate::ppm::{Pixel, Ppm};
|
use crate::image::{Pixel, Image};
|
||||||
use crate::vec2::Vec2;
|
use crate::vec2::Vec2;
|
||||||
|
|
||||||
const MAX_TABLE_SIZE: usize = 256;
|
const MAX_TABLE_SIZE: usize = 256;
|
||||||
const MAX_TABLE_SIZE_MASK: usize = MAX_TABLE_SIZE - 1;
|
const MAX_TABLE_SIZE_MASK: usize = MAX_TABLE_SIZE - 1;
|
||||||
|
|
||||||
pub fn generate_noise(width: usize, height: usize, rng: impl RngCore) -> Ppm {
|
pub fn generate_noise(width: usize, height: usize, rng: impl RngCore) -> Image {
|
||||||
let mut noise_map = vec![0.0; width * height];
|
let mut noise_map = vec![0.0; width * height];
|
||||||
let noise = ValueNoise::new(rng);
|
let noise = ValueNoise::new(rng);
|
||||||
let frequency = 0.05;
|
let frequency = 0.05;
|
||||||
|
@ -29,7 +30,7 @@ pub fn generate_noise(width: usize, height: usize, rng: impl RngCore) -> Ppm {
|
||||||
Pixel(v, v, v)
|
Pixel(v, v, v)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
Ppm {
|
Image {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
data,
|
data,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::ops::Mul;
|
use std::ops::Mul;
|
||||||
|
|
||||||
|
/// A generic 2-dimensional point class.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct Vec2<T = f64> {
|
pub struct Vec2<T = f64> {
|
||||||
pub x: T,
|
pub x: T,
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
overlays = [ fenix.overlays.default ];
|
overlays = [ fenix.overlays.default ];
|
||||||
};
|
};
|
||||||
|
|
||||||
toolchain = pkgs.fenix.default;
|
toolchain = pkgs.fenix.stable;
|
||||||
in rec {
|
in rec {
|
||||||
devShell = pkgs.mkShell {
|
devShell = pkgs.mkShell {
|
||||||
packages = (with pkgs; [ cargo-watch cargo-deny cargo-edit ])
|
packages = (with pkgs; [ cargo-watch cargo-deny cargo-edit ])
|
||||||
|
|
Loading…
Reference in a new issue