From b8ca903bba1be17643d87ee3ec95c60e83e6c20c Mon Sep 17 00:00:00 2001 From: Michael Mikovsky <77305074+Astatin3@users.noreply.github.com> Date: Wed, 22 Oct 2025 15:23:29 -0600 Subject: [PATCH] Get rendering working --- .gitignore | 14 ++ Cargo.toml | 15 ++ index.html | 19 +++ js/index.js | 26 +++ src/app.rs | 57 +++++++ src/draw.rs | 15 ++ src/lib.rs | 81 +++++++++ src/render/buffer.rs | 373 +++++++++++++++++++++++++++++++++++++++++ src/render/mod.rs | 17 ++ src/render/rand.rs | 107 ++++++++++++ src/render/renderer.rs | 117 +++++++++++++ 11 files changed, 841 insertions(+) create mode 100644 Cargo.toml create mode 100644 index.html create mode 100644 js/index.js create mode 100644 src/app.rs create mode 100644 src/draw.rs create mode 100644 src/lib.rs create mode 100644 src/render/buffer.rs create mode 100644 src/render/mod.rs create mode 100644 src/render/rand.rs create mode 100644 src/render/renderer.rs diff --git a/.gitignore b/.gitignore index 6985cf1..7e9f351 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +dist/ + # Generated by Cargo # will have compiled files and executables debug/ @@ -12,3 +14,15 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb + + +# Added by cargo + +/target + + +# Added by cargo +# +# already existing elements were commented out + +#/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0c5b949 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "IntroToWebAuthoring" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wasm-bindgen = {version = "0.2.104"} +web-sys = {version = "0.3.81", features = ['CanvasRenderingContext2d', 'Document', 'Element', 'HtmlCanvasElement', 'Window', 'ImageData']} + +# [dependencies.web-sys] +# features = +# path = "../../crates/web-sys" diff --git a/index.html b/index.html new file mode 100644 index 0000000..94f5bb8 --- /dev/null +++ b/index.html @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/js/index.js b/js/index.js new file mode 100644 index 0000000..3537bf8 --- /dev/null +++ b/js/index.js @@ -0,0 +1,26 @@ +addEventListener("TrunkApplicationStarted", (event) => { + console.log("WASM application started!"); + const canvas = document.getElementById("canvas"); + + var [width, height] = [window.innerWidth, window.innerHeight]; + + let context = window.wasmBindings.init(canvas, width, height); + + window.wasmBindings.draw(context); + + // window.wasmBindings.draw(window.innerWidth, window.innerHeight); + + addEventListener("resize", () => { + [width, height] = [window.innerWidth, window.innerHeight]; + // console.log("Window resized: (", width, ", ", height, ")"); + window.wasmBindings.resize(context, width, height); + }); + + canvas.addEventListener("click", (e) => { + // console.log(e); + + // [width, height] = [window.innerWidth, window.innerHeight]; + // console.log("Window resized: (", width, ", ", height, ")"); + window.wasmBindings.click(context, e.pageX, e.pageY); + }); +}); diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..8ddd4a0 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,57 @@ +use wasm_bindgen::prelude::wasm_bindgen; + +use crate::render::Renderer; + +pub trait Activity { + fn new() -> Self + where + Self: Sized; + fn update(&mut self, dt: f32); + fn draw(&self, ctx: &mut Renderer); +} + +#[wasm_bindgen] +pub struct App { + pub(crate) activities: Vec>, + pub(crate) renderer: Renderer, + pub(crate) current_activity: Option, +} + +impl App { + pub fn new(renderer: Renderer) -> Self { + App { + activities: Vec::new(), + renderer, + current_activity: None, + } + } + + pub fn draw(&mut self) { + // if let Some(current_activity) = self.current_activity { + // self.activities[current_activity].draw(&mut self.renderer); + // } + self.renderer.img.randomize(&mut self.renderer.rand); + + let (cx, cy) = ( + self.renderer.actual_width / 2, + self.renderer.actual_height / 2, + ); + + self.renderer + .circle(cx as i32, cy as i32, 200, (255, 255, 255)); + + self.renderer.update(); + } +} + +// App events +impl App { + pub fn resize(&mut self, width: u32, height: u32) { + self.renderer.resize(width, height); + self.draw(); + } + + pub fn click(&mut self, x: f32, y: f32) { + // self.renderer.click(x, y); + } +} diff --git a/src/draw.rs b/src/draw.rs new file mode 100644 index 0000000..1354ffb --- /dev/null +++ b/src/draw.rs @@ -0,0 +1,15 @@ +use crate::render::Renderer; + +pub fn draw(ctx: &mut Renderer) { + // // Draw the background + // ctx.background(0.0, 0.0, 0.0); + + // // Draw the foreground + // ctx.fill(1.0, 1.0, 1.0); + // ctx.rect(50.0, 50.0, 100.0, 100.0); + // + + let (cx, cy) = (ctx.actual_width / 2, ctx.actual_height / 2); + + ctx.circle(cx as i32, cy as i32, 200, (255, 255, 255)); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..a594688 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,81 @@ +mod app; +mod render; + +use wasm_bindgen::{Clamped, prelude::*}; +use web_sys::ImageData; + +// use render::Renderer; +use render::buffer::ImgBuffer; +use render::rand::Rnd; + +use crate::{app::App, render::Renderer}; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console)] + pub fn log(s: &str); +} + +#[macro_export] +macro_rules! log { + ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) +} + +#[wasm_bindgen] +pub fn init(canvas: &web_sys::HtmlCanvasElement, width: u32, height: u32) -> App { + let (cwidth, cheight, dist_x, dist_y) = render::calc_resolution(width, height); + canvas.set_width(cwidth); + canvas.set_height(cheight); + + log!("WASM Successfully initialized!"); + + let mut renderer = Renderer { + ctx: canvas + .get_context("2d") + .unwrap() + .unwrap() + .dyn_into::() + .unwrap(), + img: ImgBuffer::new(width, height), + rand: Rnd::new(9825782), + + canvas_width: cwidth, + canvas_height: cheight, + actual_width: width, + actual_height: height, + + distortion_x: dist_x, + distortion_y: dist_y, + }; + + renderer.resize(width, height); + + App::new(renderer) +} + +#[wasm_bindgen] +pub fn resize(app: &mut App, width: u32, height: u32) { + app.resize(width, height); +} + +#[wasm_bindgen] +pub fn draw(app: &mut App) { + // app.renderer.img.randomize(&mut app.renderer.rand); + app.draw(); +} + +#[wasm_bindgen] +pub fn click(app: &mut App, x: i32, y: i32) { + // ctx.img.randomize(&mut ctx.rand); + // draw::draw(ctx); + + app.renderer.circle(x, y, 20, (255, 255, 255)); + + let data = ImageData::new_with_u8_clamped_array_and_sh( + Clamped(&app.renderer.img.data), + app.renderer.img.width(), + app.renderer.img.height(), + ) + .unwrap(); + app.renderer.ctx.put_image_data(&data, 0., 0.).unwrap(); +} diff --git a/src/render/buffer.rs b/src/render/buffer.rs new file mode 100644 index 0000000..436a362 --- /dev/null +++ b/src/render/buffer.rs @@ -0,0 +1,373 @@ +// use crate::console_log; +use crate::{log, render::rand}; + +// macro_rules! log { +// ($($t:tt)*) => (console_log(&format_args!($($t)*).to_string())) +// } + +pub struct ImgBuffer { + pub data: Vec, + width: u32, + height: u32, +} + +impl ImgBuffer { + pub fn new(width: u32, height: u32) -> Self { + ImgBuffer { + data: vec![127; (width * height * 4) as usize], + width, + height, + } + } + + pub fn randomize(&mut self, rand: &mut rand::Rnd) { + for pixel in self.data.chunks_mut(4) { + pixel[0] = rand.next_i32() as u8; + pixel[1] = pixel[0]; + pixel[2] = pixel[0]; + pixel[3] = 255; + } + } + + pub fn resize(&mut self, width: u32, height: u32) { + self.data = vec![0; (width * height * 4) as usize]; + self.width = width; + self.height = height; + } + + pub fn width(&self) -> u32 { + self.width + } + + pub fn height(&self) -> u32 { + self.height + } + + pub fn to_vec(&self) -> Vec { + self.data.clone() + } +} + +impl ImgBuffer { + pub fn set_pixel(&mut self, x: u32, y: u32, color: (u8, u8, u8)) { + let index = ((y * self.width + x) * 4) as usize; + self.data[index] = color.0; + self.data[index + 1] = color.1; + self.data[index + 2] = color.2; + self.data[index + 3] = 255; + } +} + +// // use icy_sixel::{ +// // DiffusionMethod, MethodForLargest, MethodForRep, PixelFormat, Quality, sixel_string, +// // }; + +// pub struct Bitmap { +// pub data: Vec, +// pub width: usize, +// pub height: usize, +// } + +// impl Bitmap { +// pub fn new(width: usize, height: usize) -> Self { +// Self { +// data: vec![0; width * height], +// width, +// height, +// } +// } + +// pub fn from_data(data: Vec, width: usize, height: usize) -> Self { +// assert!(data.len() == width * height); +// Self { +// data, +// width, +// height, +// } +// } + +// pub fn overlay(&mut self, other: &Bitmap, xoffset: usize, yoffset: usize) { +// for y in 0..other.height { +// for x in 0..other.width { +// self.data[(y + yoffset as usize) * self.width + (x + xoffset as usize)] = +// other.data[y * other.width + x]; +// } +// } +// } + +// // /// Prints a 1 byte per pixel greyscale bitmap to Sixel format in console +// // pub fn print(&self) { +// // let mut bitmap_rgb888 = vec![0; self.width * self.height * 3]; + +// // for y in 0..self.height { +// // for x in 0..self.width { +// // let index = y * self.width + x; + +// // let pixel = self.data[index]; + +// // bitmap_rgb888[index * 3] = pixel; +// // bitmap_rgb888[index * 3 + 1] = pixel; +// // bitmap_rgb888[index * 3 + 2] = pixel; +// // } +// // } + +// // let sixel_data = sixel_string( +// // &bitmap_rgb888, +// // self.width as i32, +// // self.height as i32, +// // PixelFormat::RGB888, +// // DiffusionMethod::None, +// // MethodForLargest::Auto, +// // MethodForRep::Auto, +// // Quality::AUTO, +// // ) +// // .unwrap(); + +// // println!("{}", sixel_data); +// // } +// } + +// impl Bitmap { +// /// Sets a pixel value with bounds checking +// fn set_pixel(&mut self, x: usize, y: usize, value: u8) { +// if x < self.width && y < self.height { +// self.data[y * self.width + x] = value; +// } +// } + +// /// Gets a pixel value with bounds checking +// fn get_pixel(&self, x: usize, y: usize) -> u8 { +// if x < self.width && y < self.height { +// self.data[y * self.width + x] +// } else { +// 0 +// } +// } + +// /// Draws an antialiased line with arbitrary thickness +// pub fn draw_line( +// &mut self, +// x0: usize, +// y0: usize, +// x1: usize, +// y1: usize, +// thickness: f32, +// color: u8, +// ) { +// let x0 = x0 as f32; +// let y0 = y0 as f32; +// let x1 = x1 as f32; +// let y1 = y1 as f32; + +// let dx = x1 - x0; +// let dy = y1 - y0; +// let length = (dx * dx + dy * dy).sqrt(); + +// if length < 0.001 { +// // Handle degenerate case of zero-length line +// // self.draw_thick_point(x0 as usize, y0 as usize, thickness, color); +// return; +// } + +// // Unit vector perpendicular to the line +// // let perp_x = -dy / length; +// // let perp_y = dx / length; + +// // Half thickness for calculations +// let half_thickness = thickness * 0.5; + +// // Calculate bounding box with some padding for antialiasing +// let padding = (thickness * 0.5 + 1.0).ceil() as i32; +// let min_x = ((x0.min(x1) - padding as f32).floor() as i32).max(0) as usize; +// let max_x = +// ((x0.max(x1) + padding as f32).ceil() as i32).min(self.width as i32 - 1) as usize; +// let min_y = ((y0.min(y1) - padding as f32).floor() as i32).max(0) as usize; +// let max_y = +// ((y0.max(y1) + padding as f32).ceil() as i32).min(self.height as i32 - 1) as usize; + +// // For each pixel in the bounding box, calculate distance to line +// for y in min_y..=max_y { +// for x in min_x..=max_x { +// let px = x as f32; +// let py = y as f32; + +// // Calculate distance from point to line segment +// let distance = self.point_to_line_segment_distance(px, py, x0, y0, x1, y1); + +// // Calculate alpha based on distance and thickness +// let alpha = self.calculate_alpha(distance, half_thickness); + +// if alpha > 0.0 { +// self.blend_pixel(x, y, color, alpha); +// } +// } +// } +// } + +// /// Blends a pixel with the existing value using alpha blending +// fn blend_pixel(&mut self, x: usize, y: usize, color: u8, alpha: f32) { +// if x < self.width && y < self.height { +// let existing = self.get_pixel(x, y) as f32; +// let new_value = (existing * (1.0 - alpha) + color as f32 * alpha).round() as u8; +// self.set_pixel(x, y, new_value); +// } +// } + +// /// Calculates the shortest distance from a point to a line segment +// fn point_to_line_segment_distance( +// &self, +// px: f32, +// py: f32, +// x0: f32, +// y0: f32, +// x1: f32, +// y1: f32, +// ) -> f32 { +// let dx = x1 - x0; +// let dy = y1 - y0; +// let length_sq = dx * dx + dy * dy; + +// if length_sq < 0.001 { +// // Line segment is actually a point +// let dpx = px - x0; +// let dpy = py - y0; +// return (dpx * dpx + dpy * dpy).sqrt(); +// } + +// // Calculate parameter t for the closest point on the line segment +// let t = ((px - x0) * dx + (py - y0) * dy) / length_sq; +// let t = t.max(0.0).min(1.0); // Clamp to [0, 1] to stay on segment + +// // Find the closest point on the line segment +// let closest_x = x0 + t * dx; +// let closest_y = y0 + t * dy; + +// // Return distance to closest point +// let dpx = px - closest_x; +// let dpy = py - closest_y; +// (dpx * dpx + dpy * dpy).sqrt() +// } + +// /// Calculates alpha value based on distance from line edge +// fn calculate_alpha(&self, distance: f32, half_thickness: f32) -> f32 { +// if distance <= half_thickness - 0.5 { +// // Inside the line core - full opacity +// 1.0 +// } else if distance <= half_thickness + 0.5 { +// // In the antialiasing zone - linear falloff +// half_thickness + 0.5 - distance +// } else { +// // Outside the line - transparent +// 0.0 +// } +// } + +// // // Draw a thick line by creating a capsule shape +// // fn draw_line(&mut self, x1: usize, y1: usize, x2: usize, y2: usize, width: usize, color: u8) { +// // let half_width = width as f32 / 2.0; + +// // // Calculate line vector and length +// // let dx = (x2 - x1) as f32; +// // let dy = (y2 - y1) as f32; +// // let length = (dx * dx + dy * dy).sqrt(); + +// // // if length < 0.001 { +// // // // Degenerate case: just draw a circle +// // // self.draw_circle(x1, y1, half_width as i32, color); +// // // return; +// // // } + +// // // Normalize the line direction +// // let nx = dx / length; +// // let ny = dy / length; + +// // // Calculate perpendicular vector for line thickness +// // let px = -ny * half_width; +// // let py = nx * half_width; + +// // // Get the four corners of the rectangle +// // let corner1_x = (x1 as f32 + px) as usize; +// // let corner1_y = (y1 as f32 + py) as usize; +// // let corner2_x = (x1 as f32 - px) as usize; +// // let corner2_y = (y1 as f32 - py) as usize; +// // let corner3_x = (x2 as f32 - px) as usize; +// // let corner3_y = (y2 as f32 - py) as usize; +// // let corner4_x = (x2 as f32 + px) as usize; +// // let corner4_y = (y2 as f32 + py) as usize; + +// // // Draw the main rectangle body +// // self.fill_quadrilateral( +// // corner1_x, corner1_y, corner2_x, corner2_y, corner3_x, corner3_y, corner4_x, corner4_y, +// // color, +// // ); + +// // // // Draw rounded ends +// // // let radius = (half_width) as i32; +// // // self.draw_circle(x1, y1, radius, color); +// // // self.draw_circle(x2, y2, radius, color); +// // } + +// // fn fill_quadrilateral( +// // &mut self, +// // x1: usize, +// // y1: usize, +// // x2: usize, +// // y2: usize, +// // x3: usize, +// // y3: usize, +// // x4: usize, +// // y4: usize, +// // color: u8, +// // ) { +// // // Find bounding box +// // let binding_x = [x1, x2, x3, x4]; +// // let binding_y = [y1, y2, y3, y4]; +// // let min_x = binding_x.iter().min().unwrap(); +// // let max_x = binding_x.iter().max().unwrap(); +// // let min_y = binding_y.iter().min().unwrap(); +// // let max_y = binding_y.iter().max().unwrap(); + +// // // For each point in bounding box, test if it's inside the quadrilateral +// // for y in *min_y..=*max_y { +// // for x in *min_x..=*max_x { +// // if self.point_in_quad(x, y, x1, y1, x2, y2, x3, y3, x4, y4) { +// // self.set_pixel(x, y, color); +// // } +// // } +// // } +// // } + +// // // Test if a point is inside a quadrilateral using cross products +// // fn point_in_quad( +// // &self, +// // px: usize, +// // py: usize, +// // x1: usize, +// // y1: usize, +// // x2: usize, +// // y2: usize, +// // x3: usize, +// // y3: usize, +// // x4: usize, +// // y4: usize, +// // ) -> bool { +// // // Test against each edge of the quadrilateral +// // let sign1 = self.cross_product(px - x1, py - y1, x2 - x1, y2 - y1); +// // let sign2 = self.cross_product(px - x2, py - y2, x3 - x2, y3 - y2); +// // let sign3 = self.cross_product(px - x3, py - y3, x4 - x3, y4 - y3); +// // let sign4 = self.cross_product(px - x4, py - y4, x1 - x4, y1 - y4); + +// // // Point is inside if all cross products have the same sign +// // (sign1 >= 0 && sign2 >= 0 && sign3 >= 0 && sign4 >= 0) +// // || (sign1 <= 0 && sign2 <= 0 && sign3 <= 0 && sign4 <= 0) +// // } + +// // // Calculate 2D cross product +// // fn cross_product(&self, ax: usize, ay: usize, bx: usize, by: usize) -> usize { +// // ax * by - ay * bx +// // } + +// // fn set_pixel(&mut self, x: usize, y: usize, color: u8) { +// // self.data[y*self.width + x] = color; +// // } +// } diff --git a/src/render/mod.rs b/src/render/mod.rs new file mode 100644 index 0000000..faa1fc2 --- /dev/null +++ b/src/render/mod.rs @@ -0,0 +1,17 @@ +pub mod buffer; +pub mod rand; +mod renderer; + +pub use renderer::Renderer; + +pub const RESOLUTION: u32 = 800; + +pub fn calc_resolution(width: u32, height: u32) -> (u32, u32, f32, f32) { + let aspect = width as f32 / height as f32; + + let (distortion_x, distortion_y) = (aspect, 1. / aspect); + + let new_width = (distortion_x * RESOLUTION as f32).round() as u32; + let new_height = (distortion_y * RESOLUTION as f32).round() as u32; + (new_width, new_height, distortion_x, distortion_y) +} diff --git a/src/render/rand.rs b/src/render/rand.rs new file mode 100644 index 0000000..c1e26f9 --- /dev/null +++ b/src/render/rand.rs @@ -0,0 +1,107 @@ +pub struct Rnd { + Seed: u32, +} + +impl Rnd { + const OFFSET: u32 = 0x5414d5f4; + + #[inline] + pub fn new(seed: u32) -> Self { + Self { + Seed: seed ^ Rnd::OFFSET, + } + } + + #[inline] + pub fn new_randomized() -> Self { + Self { + Seed: Rnd::OFFSET.wrapping_mul(Rnd::generate_time_seed()), + } + } + + fn generate_time_seed() -> u32 { + use std::time::{SystemTime, UNIX_EPOCH}; + let now = SystemTime::now(); + let since_epoch = now.duration_since(UNIX_EPOCH); + match since_epoch { + Ok(d) => { + let ms: u128 = d.as_micros(); + let a = (ms >> 96) as u32; + let b = ((ms >> 64) & 0xffffffff) as u32; + let c = ((ms >> 32) & 0xffffffff) as u32; + let d = ms as u32; + a ^ b ^ c ^ d + } + _ => Rnd::OFFSET, + } + } + + #[inline] + pub fn next_u32(&mut self) -> u32 { + let mut random: u32 = self.Seed; + random ^= random << 13; + random ^= random >> 17; + random ^= random << 5; + self.Seed ^= random; + random + } + + #[inline] + pub fn next_u64(&mut self) -> u64 { + let a: u64 = self.next_u32() as u64; + let b: u64 = self.next_u32() as u64; + a | (b << 32) + } + + #[inline] + pub fn next_i64(&mut self) -> i64 { + self.next_u64() as i64 + } + + #[inline] + pub fn next_i32(&mut self) -> i32 { + self.next_u32() as i32 + } + + #[inline] + pub fn next_u64_range(&mut self, min: u64, max: u64) -> u64 { + if max > min { + let range = max - min; + min + (self.next_u64() % range) + } else { + min + } + } + + #[inline] + pub fn next_u32_range(&mut self, min: u32, max: u32) -> u32 { + if max > min { + let range = max - min; + min + (self.next_u32() % range) + } else { + min + } + } + + #[inline] + pub fn next_i64_range(&mut self, min: i64, max: i64) -> i64 { + if max > min { + let range = (max - min) as u64; + let r = self.next_u64() % range; + (r as i64) + min + } else { + min + } + } + + #[inline] + pub fn next_i32_range(&mut self, min: i32, max: i32) -> i32 { + if max > min { + let range = (max - min) as u32; + let r = self.next_u32() % range; + (r as i32) + min + } else { + min + } + } +} diff --git a/src/render/renderer.rs b/src/render/renderer.rs new file mode 100644 index 0000000..16a7d67 --- /dev/null +++ b/src/render/renderer.rs @@ -0,0 +1,117 @@ +use wasm_bindgen::{Clamped, prelude::*}; +use web_sys::ImageData; + +use crate::{ + log, + render::{buffer::ImgBuffer, calc_resolution, rand::Rnd}, +}; + +#[wasm_bindgen] +pub struct Renderer { + pub(crate) ctx: web_sys::CanvasRenderingContext2d, + pub(crate) img: ImgBuffer, + pub(crate) rand: Rnd, + + pub canvas_width: u32, + pub canvas_height: u32, + pub actual_width: u32, + pub actual_height: u32, + + pub distortion_x: f32, + pub distortion_y: f32, +} + +impl Renderer { + pub fn new(canvas: &web_sys::HtmlCanvasElement, width: u32, height: u32) -> Self { + let (cwidth, cheight, dist_x, dist_y) = calc_resolution(width, height); + + // let ctx = canvas.get_context("2d").unwrap().unwrap(); + let img = ImgBuffer::new(cwidth, cheight); + let rand = Rnd::new(12345); + + Self { + ctx: canvas + .get_context("2d") + .unwrap() + .unwrap() + .dyn_into::() + .unwrap(), + img, + rand, + + canvas_width: cwidth, + canvas_height: cheight, + actual_width: width, + actual_height: height, + distortion_x: dist_x, + distortion_y: dist_y, + } + } + + pub fn resize(&mut self, width: u32, height: u32) { + let (cwidth, cheight, dist_x, dist_y) = calc_resolution(width, height); + + self.canvas_width = cwidth; + self.canvas_height = cheight; + self.actual_width = width; + self.actual_height = height; + self.distortion_x = dist_x; + self.distortion_y = dist_y; + + self.img.resize(cwidth, cheight); + self.ctx.canvas().unwrap().set_width(cwidth); + self.ctx.canvas().unwrap().set_height(cheight); + } + + pub fn update(&mut self) { + let data = ImageData::new_with_u8_clamped_array_and_sh( + Clamped(&self.img.data), + self.img.width(), + self.img.height(), + ) + .unwrap(); + self.ctx.put_image_data(&data, 0., 0.).unwrap(); + } +} + +impl Renderer { + pub fn undistort(&self, x: f32, y: f32) -> (f32, f32) { + ( + x * (self.canvas_width as f32 / self.actual_width as f32), + y * (self.canvas_height as f32 / self.actual_height as f32), + ) + } + + pub fn screen_dist_sq(&self, x: f32, y: f32, rx: f32, ry: f32) -> f32 { + (rx - x).powf(2.) * self.distortion_y + (ry - y).powf(2.) * self.distortion_x + } + + pub fn circle(&mut self, cx: i32, cy: i32, radius: i32, color: (u8, u8, u8)) { + let radius = radius as f32; + + let (leftx, topy) = self.undistort(cx as f32 - radius, cy as f32 - radius); + let (rightx, bottomy) = self.undistort(cx as f32 + radius, cy as f32 + radius); + + let (cx, cy) = self.undistort(cx as f32, cy as f32); + + let leftx = leftx.max(0f32); + let topy = topy.max(0f32); + let rightx = rightx.min(self.canvas_width as f32); + let bottomy = bottomy.min(self.canvas_height as f32); + + let e = (self.canvas_height as f32 / self.actual_height as f32) + * (self.canvas_width as f32 / self.actual_width as f32); + + let r2 = (radius).powf(2.) * e; + + // log!("{}, {}, {}, {}", leftx, rightx, topy, bottomy); + + for x in leftx as i32..rightx as i32 { + for y in topy as i32..bottomy as i32 { + if self.screen_dist_sq(x as f32, y as f32, cx, cy) <= r2 { + self.img.set_pixel(x as u32, y as u32, color); + } + } + } + } +}