mirror of
https://github.com/Astatin3/IntroToWebAuthoring.git
synced 2026-06-08 16:18:01 -06:00
Get rendering working
This commit is contained in:
+14
@@ -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
|
||||
|
||||
+15
@@ -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"
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<html>
|
||||
<head>
|
||||
<link data-trunk rel="rust" />
|
||||
<style>
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background-color: black;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas
|
||||
id="canvas"
|
||||
style="position: fixed; width: 100%; height: 100%"
|
||||
></canvas>
|
||||
<script data-trunk src="js/index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+26
@@ -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);
|
||||
});
|
||||
});
|
||||
+57
@@ -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<Box<dyn Activity>>,
|
||||
pub(crate) renderer: Renderer,
|
||||
pub(crate) current_activity: Option<usize>,
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
+15
@@ -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));
|
||||
}
|
||||
+81
@@ -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::<web_sys::CanvasRenderingContext2d>()
|
||||
.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();
|
||||
}
|
||||
@@ -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<u8>,
|
||||
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<u8> {
|
||||
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<u8>,
|
||||
// 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<u8>, 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;
|
||||
// // }
|
||||
// }
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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::<web_sys::CanvasRenderingContext2d>()
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user