Skip to content

Instantly share code, notes, and snippets.

@garentyler
Created March 18, 2022 03:23
Show Gist options
  • Save garentyler/cf64f0e4986f72636496a08aa813d312 to your computer and use it in GitHub Desktop.
Save garentyler/cf64f0e4986f72636496a08aa813d312 to your computer and use it in GitHub Desktop.
Math IA Graphing Program
[package]
name = "graphing"
version = "0.1.0"
edition = "2021"
[dependencies]
image = "*"
num-complex = "*"
rayon = "*"
serde = { version = "*", features = [ "derive" ] }
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()
}
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