Created
March 18, 2022 03:23
-
-
Save garentyler/cf64f0e4986f72636496a08aa813d312 to your computer and use it in GitHub Desktop.
Math IA Graphing Program
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "graphing" | |
version = "0.1.0" | |
edition = "2021" | |
[dependencies] | |
image = "*" | |
num-complex = "*" | |
rayon = "*" | |
serde = { version = "*", features = [ "derive" ] } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
mod tools; | |
use crate::tools::*; | |
use num_complex::Complex; | |
use rayon::prelude::*; | |
use serde::{Deserialize, Serialize}; | |
use std::io::{stdout, Write}; | |
#[derive(Debug, Serialize, Deserialize)] | |
struct AudioTrack { | |
pub sample_rate: f64, | |
pub samples: Vec<f64>, | |
} | |
fn main() { | |
let red = [255, 0, 0]; | |
let blue = [0, 0, 255]; | |
let screen = Size::new(0, 1600, 0, 1600); | |
let points_world = Size::new(-10.0, 10.0, -10.0, 10.0); | |
let rotated_world = Size::new(-4.0, 4.0, -4.0, 4.0); | |
let frequency_world = Size::new(-0.5, 19.5, -0.1, 0.65); | |
let points_sample_delta = 0.001; | |
let frequency_sample_delta = 0.0001; | |
let function = move |x: f64| -> f64 { | |
let mut out = 0.0; | |
out += (x * 2.0).sin(); | |
out += (x * 3.0).sin(); | |
out | |
}; | |
// Sample and get complex points. | |
print!("Generating points..."); | |
let _ = stdout().flush(); | |
let points = calculate_points( | |
(points_world.x_min, points_world.x_max), | |
points_sample_delta, | |
&function, | |
); | |
println!("\tdone!"); | |
// Rotate the points | |
print!("Rotating points..."); | |
let _ = stdout().flush(); | |
let rotated_points = rotate_points(2.0, &points); | |
println!("\tdone!"); | |
// Get the frequency chart | |
print!("Generating frequency map..."); | |
let _ = stdout().flush(); | |
let frequencies = calculate_frequencies((0.0, frequency_world.x_max), frequency_sample_delta, &points); | |
println!("\tdone!"); | |
println!("Generating graphs..."); | |
{ | |
let points = points | |
.iter() | |
.map(|p| Point::new(p.0, p.1, red, true)) | |
.collect::<Vec<Point<f64>>>(); | |
write_points_with_grid(screen, points_world, (1.0, 1.0), "points.png", &points); | |
println!("\tpoints.png"); | |
let rotated_average = average_point(&rotated_points); | |
let mut rotated_points = rotated_points | |
.iter() | |
.map(|p| Point::new(p.0, p.1, red, true)) | |
.collect::<Vec<Point<f64>>>(); | |
rotated_points.push(Point::new(rotated_average.0, rotated_average.1, blue, true)); | |
write_points_with_grid( | |
screen, | |
rotated_world, | |
(1.0, 1.0), | |
"rotated.png", | |
&rotated_points, | |
); | |
println!("\trotated.png"); | |
let frequencies = frequencies | |
.iter() | |
.map(|p| Point::new(p.0, p.1, red, false)) | |
.collect::<Vec<Point<f64>>>(); | |
write_points_with_grid( | |
screen, | |
frequency_world, | |
(1.0, 0.1), | |
"frequencies.png", | |
&frequencies, | |
); | |
println!("\tfrequencies.png"); | |
} | |
println!("\tdone!"); | |
} | |
pub fn calculate_points( | |
sample_range: (f64, f64), | |
sample_delta: f64, | |
function: &dyn Fn(f64) -> f64, | |
) -> Vec<(f64, f64)> { | |
let _points: Vec<(f64, f64)>; | |
let mut samples = vec![]; | |
let mut x = sample_range.0; | |
while x < sample_range.1 { | |
samples.push(x); | |
x += sample_delta; | |
} | |
let _ = stdout().flush(); | |
samples.iter().map(|&x| (x, function(x))).collect() | |
} | |
fn rotate_points(winding_frequency: f64, points: &[(f64, f64)]) -> Vec<(f64, f64)> { | |
points | |
.par_iter() | |
.map(|&point| { | |
let radius = point.1; | |
let theta = point.0 * winding_frequency; | |
let num = Complex::from_polar(radius, theta); | |
(num.re, num.im) | |
}) | |
.collect() | |
} | |
fn average_point(points: &[(f64, f64)]) -> (f64, f64) { | |
let mut sum_x = 0.0; | |
let mut sum_y = 0.0; | |
for point in points { | |
sum_x += point.0; | |
sum_y += point.1; | |
} | |
let avg_x = sum_x / points.len() as f64; | |
let avg_y = sum_y / points.len() as f64; | |
(avg_x, avg_y) | |
} | |
fn distance(points: &[(f64, f64)]) -> f64 { | |
let (avg_x, avg_y) = average_point(points); | |
(avg_x * avg_x + avg_y * avg_y).sqrt() | |
} | |
fn calculate_frequencies( | |
frequency_range: (f64, f64), | |
frequency_delta: f64, | |
points: &[(f64, f64)], | |
) -> Vec<(f64, f64)> { | |
let mut samples = vec![]; | |
let mut x = frequency_range.0; | |
while x < frequency_range.1 { | |
samples.push(x); | |
x += frequency_delta; | |
} | |
samples | |
.par_iter() | |
.map(|&f| (f, distance(&rotate_points(f, points)))) | |
.collect() | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::ops::Sub; | |
#[derive(Copy, Clone)] | |
pub struct Size<T: Sub<Output = T> + Copy + Clone> { | |
pub x_min: T, | |
pub x_max: T, | |
pub y_min: T, | |
pub y_max: T, | |
} | |
impl<T: Sub<Output = T> + Copy + Clone> Size<T> { | |
pub fn new(x_min: T, x_max: T, y_min: T, y_max: T) -> Size<T> { | |
Size { | |
x_max, | |
x_min, | |
y_min, | |
y_max, | |
} | |
} | |
pub fn width(&self) -> T { | |
self.x_max - self.x_min | |
} | |
pub fn height(&self) -> T { | |
self.y_max - self.y_min | |
} | |
} | |
#[derive(Copy, Clone)] | |
pub struct Point<T: Copy + Clone> { | |
pub x: T, | |
pub y: T, | |
pub color: [u8; 3], | |
pub thick: bool, | |
} | |
impl<T: Copy + Clone> Point<T> { | |
pub fn new(x: T, y: T, color: [u8; 3], thick: bool) -> Point<T> { | |
Point { x, y, color, thick } | |
} | |
} | |
pub fn map(value: f64, start_range: (f64, f64), end_range: (f64, f64)) -> f64 { | |
let start_range_length = start_range.1 - start_range.0; | |
let end_range_length = end_range.1 - end_range.0; | |
let percent = (value - start_range.0) / start_range_length; | |
percent * end_range_length + end_range.0 | |
} | |
pub fn world_to_screen(point: Point<f64>, screen: Size<u32>, world: Size<f64>) -> Point<u32> { | |
Point { | |
x: map( | |
point.x, | |
(world.x_min, world.x_max), | |
(screen.x_min as f64, screen.x_max as f64), | |
) as u32, | |
y: map( | |
point.y, | |
(world.y_min, world.y_max), | |
(screen.y_min as f64, screen.y_max as f64), | |
) as u32, | |
color: point.color, | |
thick: point.thick, | |
} | |
} | |
pub fn points_to_screen( | |
screen: Size<u32>, | |
world: Size<f64>, | |
points: &[Point<f64>], | |
) -> Vec<Point<u32>> { | |
let mut screen_points = vec![]; | |
for point in points { | |
if point.x < world.x_min | |
|| point.x > world.x_max | |
|| point.y < world.y_min | |
|| point.y > world.y_max | |
{ | |
continue; | |
} | |
let screen_point = world_to_screen(*point, screen, world); | |
screen_points.push(screen_point); | |
} | |
screen_points | |
} | |
pub fn write_screen_points(screen: Size<u32>, filename: &str, points: &[Point<u32>]) { | |
let mut imgbuf = image::ImageBuffer::new(screen.width(), screen.height()); | |
for (_x, _y, pixel) in imgbuf.enumerate_pixels_mut() { | |
*pixel = image::Rgb([255u8, 255u8, 255u8]); | |
} | |
for point in points { | |
if point.x >= screen.x_max || point.y >= screen.y_max { | |
continue; | |
} | |
*imgbuf.get_pixel_mut(point.x, point.y) = image::Rgb(point.color); | |
if point.thick { | |
if point.x > screen.x_min + 1 { | |
*imgbuf.get_pixel_mut(point.x - 1, point.y) = image::Rgb(point.color); | |
} | |
if point.y > screen.y_min + 1 { | |
*imgbuf.get_pixel_mut(point.x, point.y - 1) = image::Rgb(point.color); | |
} | |
if point.x + 1 < screen.x_max { | |
*imgbuf.get_pixel_mut(point.x + 1, point.y) = image::Rgb(point.color); | |
} | |
if point.y + 1 < screen.y_max { | |
*imgbuf.get_pixel_mut(point.x, point.y + 1) = image::Rgb(point.color); | |
} | |
} | |
} | |
image::imageops::flip_vertical_in_place(&mut imgbuf); | |
imgbuf.save(filename).unwrap(); | |
} | |
pub fn write_points_with_grid( | |
screen: Size<u32>, | |
world: Size<f64>, | |
grid_resolution: (f64, f64), | |
filename: &str, | |
points: &[Point<f64>], | |
) { | |
let mut axes = vec![]; | |
axes.append(&mut line( | |
(world.x_min, 0.0), | |
(world.x_max, 0.0), | |
screen.width() as usize + 1, | |
[0, 0, 0], | |
true, | |
)); | |
axes.append(&mut line( | |
(0.0, world.y_min), | |
(0.0, world.y_max), | |
screen.height() as usize + 1, | |
[0, 0, 0], | |
true, | |
)); | |
let mut grid = vec![]; | |
{ | |
let mut x = 0.0; | |
while x < world.x_max { | |
grid.append(&mut line( | |
(x as f64, world.y_min), | |
(x as f64, world.y_max), | |
screen.width() as usize + 1, | |
[128, 128, 128], | |
false, | |
)); | |
x += grid_resolution.0; | |
} | |
x = 0.0; | |
while x > world.x_min { | |
grid.append(&mut line( | |
(x as f64, world.y_min), | |
(x as f64, world.y_max), | |
screen.width() as usize + 1, | |
[128, 128, 128], | |
false, | |
)); | |
x -= grid_resolution.0; | |
} | |
} | |
{ | |
let mut y = 0.0; | |
while y < world.y_max { | |
grid.append(&mut line( | |
(world.x_min, y as f64), | |
(world.x_max, y as f64), | |
screen.height() as usize + 1, | |
[128, 128, 128], | |
false, | |
)); | |
y += grid_resolution.1; | |
} | |
y = 0.0; | |
while y > world.y_min { | |
grid.append(&mut line( | |
(world.x_min, y as f64), | |
(world.x_max, y as f64), | |
screen.height() as usize + 1, | |
[128, 128, 128], | |
false, | |
)); | |
y -= grid_resolution.1; | |
} | |
} | |
write_screen_points(screen, filename, &{ | |
let mut p = vec![]; | |
p.extend_from_slice(&(points_to_screen(screen, world, &grid))); | |
p.extend_from_slice(&(points_to_screen(screen, world, &axes))); | |
p.extend_from_slice(&(points_to_screen(screen, world, points))); | |
p | |
}); | |
} | |
pub fn line( | |
start: (f64, f64), | |
end: (f64, f64), | |
num_samples: usize, | |
color: [u8; 3], | |
thick: bool, | |
) -> Vec<Point<f64>> { | |
let mut points = vec![]; | |
let delta = ((end.0 - start.0).abs(), (end.1 - start.1).abs()); | |
for i in 0..num_samples { | |
let percent = i as f64 / num_samples as f64; | |
let point = Point::new( | |
start.0 + delta.0 * percent, | |
start.1 + delta.1 * percent, | |
color, | |
thick, | |
); | |
points.push(point); | |
} | |
points | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment