feat: main page background #1

Merged
zacheryasc merged 1 commit from fractal-engine into master 2026-02-06 04:50:51 +00:00
10 changed files with 352 additions and 23 deletions
Showing only changes of commit a5d56c0bd2 - Show all commits

4
.gitignore vendored
View file

@ -1,3 +1,5 @@
/target
/web-build
.local
.local
**.bak
**/dist/

19
Cargo.lock generated
View file

@ -331,6 +331,15 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fractal-engine"
version = "0.1.0"
dependencies = [
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "futures-channel"
version = "0.3.31"
@ -1104,6 +1113,16 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "web-sys"
version = "0.3.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "windows-link"
version = "0.2.1"

View file

@ -1,6 +1,7 @@
[workspace]
members = [
"crates/cli",
"crates/fractal-engine",
"crates/message-tools",
"crates/server"
]

View file

@ -255,7 +255,7 @@ fn handle_command_status(status_result: std::io::Result<ExitStatus>, command_nam
}
fn build() {
println!("Building wasm package...");
println!("Building wasm package (message-tools)...");
// build the wasm package
let status = Command::new("wasm-pack")
.arg("build")
@ -265,7 +265,18 @@ fn build() {
.arg("--target")
.arg("no-modules")
.status();
handle_command_status(status, "wasm-pack build");
handle_command_status(status, "wasm-pack build message-tools");
println!("Building wasm package (fractal-engine)...");
let status = Command::new("wasm-pack")
.arg("build")
.arg("--out-dir")
.arg("../../routes/root/pkg")
.arg("crates/fractal-engine")
.arg("--target")
.arg("no-modules")
.status();
handle_command_status(status, "wasm-pack build fractal-engine");
println!("Building server executable...");
// build the server executable
@ -285,10 +296,10 @@ fn build() {
handle_command_status(status, "mkdir web-build");
println!("Copying files...");
// copy relevant files
// copy relevant files (use trailing slash to merge into existing directory)
let status = Command::new("cp")
.args(["-R", "-f", "routes"])
.arg(format!("{WEB_BUILD_DIR}/routes"))
.arg(WEB_BUILD_DIR)
.status();
handle_command_status(status, "cp routes");

View file

@ -0,0 +1,15 @@
[package]
name = "fractal-engine"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib", "rlib"]
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
wasm-bindgen = "0.2.105"
web-sys = { version = "0.3", features = [
"Window", "Document", "HtmlCanvasElement",
"CanvasRenderingContext2d", "ImageData"
]}
js-sys = "0.3"

View file

@ -0,0 +1,25 @@
/// Ocean color palette for Newton's fractal
/// Ocean color palette for three roots
/// root_index: 0, 1, or 2
/// iterations: number of iterations to converge (affects brightness)
/// max_iterations: maximum iterations (for normalization)
pub fn ocean_palette(root_index: u8, iterations: u32, max_iterations: u32) -> (u8, u8, u8) {
// Brightness based on iteration count - fewer iterations = brighter
let t = (iterations as f64) / (max_iterations as f64);
let brightness = (1.0 - t * 0.7).max(0.3);
// Ocean theme colors for each root basin
let (r, g, b) = match root_index {
0 => (0.05, 0.25, 0.55), // Deep ocean blue
1 => (0.0, 0.45, 0.50), // Teal
2 => (0.15, 0.55, 0.65), // Cyan/turquoise
_ => (0.05, 0.25, 0.55),
};
(
(r * brightness * 255.0) as u8,
(g * brightness * 255.0) as u8,
(b * brightness * 255.0) as u8,
)
}

View file

@ -0,0 +1,5 @@
pub mod color;
pub mod newton;
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
pub mod wasm;

View file

@ -0,0 +1,76 @@
/// Newton's method fractal for z^3 - 1
/// The three roots are at 120 degree intervals on the unit circle
const MAX_ITERATIONS: u32 = 32;
const TOLERANCE_SQ: f64 = 1e-6;
/// Compute Newton's method iteration for z^3 - 1
/// Returns (root_index, iterations) where root_index is 0, 1, or 2
/// Time parameter rotates the viewing angle for animation
pub fn compute(z_re: f64, z_im: f64, time: f64) -> (u8, u32) {
// Rotate input coordinates to animate the fractal
let cos_t = time.cos();
let sin_t = time.sin();
let mut zr = z_re * cos_t - z_im * sin_t;
let mut zi = z_re * sin_t + z_im * cos_t;
// The three roots of z^3 - 1
const ROOTS: [(f64, f64); 3] = [
(1.0, 0.0), // 1
(-0.5, 0.866025403784439), // e^(2πi/3)
(-0.5, -0.866025403784439), // e^(4πi/3)
];
for iter in 0..MAX_ITERATIONS {
// Compute z^2
let z2_re = zr * zr - zi * zi;
let z2_im = 2.0 * zr * zi;
// Compute z^3
let z3_re = zr * z2_re - zi * z2_im;
let z3_im = zr * z2_im + zi * z2_re;
// Check distance to each root
for (idx, &(root_re, root_im)) in ROOTS.iter().enumerate() {
let dr = zr - root_re;
let di = zi - root_im;
if dr * dr + di * di < TOLERANCE_SQ {
return (idx as u8, iter);
}
}
// Newton's method: z = z - (z^3 - 1)/(3z^2)
// Simplifies to: z = (2z^3 + 1)/(3z^2)
// Denominator: 3z^2
let denom_re = 3.0 * z2_re;
let denom_im = 3.0 * z2_im;
let denom_mag_sq = denom_re * denom_re + denom_im * denom_im;
if denom_mag_sq < 1e-12 {
return (0, MAX_ITERATIONS);
}
// Numerator: 2z^3 + 1
let num_re = 2.0 * z3_re + 1.0;
let num_im = 2.0 * z3_im;
// Complex division
zr = (num_re * denom_re + num_im * denom_im) / denom_mag_sq;
zi = (num_im * denom_re - num_re * denom_im) / denom_mag_sq;
}
// Find closest root
let mut closest = 0u8;
let mut min_dist = f64::MAX;
for (idx, &(root_re, root_im)) in ROOTS.iter().enumerate() {
let dr = zr - root_re;
let di = zi - root_im;
let dist = dr * dr + di * di;
if dist < min_dist {
min_dist = dist;
closest = idx as u8;
}
}
(closest, MAX_ITERATIONS)
}

View file

@ -0,0 +1,106 @@
use wasm_bindgen::prelude::*;
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, ImageData};
use crate::color::ocean_palette;
use crate::newton::compute;
const MAX_ITERATIONS: u32 = 32;
/// Get the canvas element and 2D context
fn get_canvas_and_context(canvas_id: &str) -> Result<(HtmlCanvasElement, CanvasRenderingContext2d), JsValue> {
let window = web_sys::window().ok_or("no window")?;
let document = window.document().ok_or("no document")?;
let canvas = document
.get_element_by_id(canvas_id)
.ok_or("no canvas element")?
.dyn_into::<HtmlCanvasElement>()?;
let context = canvas
.get_context("2d")?
.ok_or("no 2d context")?
.dyn_into::<CanvasRenderingContext2d>()?;
Ok((canvas, context))
}
/// Render the Newton fractal with animation (time parameter rotates the view)
#[wasm_bindgen]
pub fn render_fractal_animated(canvas_id: &str, time: f64, scale: u32) -> Result<(), JsValue> {
let (canvas, context) = get_canvas_and_context(canvas_id)?;
let canvas_width = canvas.width() as usize;
let canvas_height = canvas.height() as usize;
// Render at reduced resolution
let scale = scale.max(1) as usize;
let width = canvas_width / scale;
let height = canvas_height / scale;
// Create RGBA buffer
let mut pixels: Vec<u8> = vec![0; width * height * 4];
// Complex plane bounds
let view_size = 3.0;
let aspect = width as f64 / height as f64;
for y in 0..height {
for x in 0..width {
// Map pixel to complex plane (centered at origin)
let z_re = (x as f64 / width as f64 - 0.5) * view_size * aspect;
let z_im = (y as f64 / height as f64 - 0.5) * view_size;
// Compute Newton iteration
let (root_index, iterations) = compute(z_re, z_im, time);
// Get color from ocean palette
let (r, g, b) = ocean_palette(root_index, iterations, MAX_ITERATIONS);
// Write to pixel buffer (RGBA)
let idx = (y * width + x) * 4;
pixels[idx] = r;
pixels[idx + 1] = g;
pixels[idx + 2] = b;
pixels[idx + 3] = 255;
}
}
// Create ImageData at reduced size
let clamped = wasm_bindgen::Clamped(&pixels[..]);
let image_data = ImageData::new_with_u8_clamped_array_and_sh(clamped, width as u32, height as u32)?;
// Scale up when drawing to canvas
if scale > 1 {
// Draw to a temporary position then scale
context.put_image_data(&image_data, 0.0, 0.0)?;
// Use drawImage to scale up
let temp_canvas = document_create_canvas(width as u32, height as u32)?;
let temp_ctx = temp_canvas
.get_context("2d")?
.ok_or("no temp 2d context")?
.dyn_into::<CanvasRenderingContext2d>()?;
temp_ctx.put_image_data(&image_data, 0.0, 0.0)?;
context.set_image_smoothing_enabled(false);
context.draw_image_with_html_canvas_element_and_dw_and_dh(
&temp_canvas,
0.0, 0.0,
canvas_width as f64, canvas_height as f64
)?;
} else {
context.put_image_data(&image_data, 0.0, 0.0)?;
}
Ok(())
}
fn document_create_canvas(width: u32, height: u32) -> Result<HtmlCanvasElement, JsValue> {
let window = web_sys::window().ok_or("no window")?;
let document = window.document().ok_or("no document")?;
let canvas = document
.create_element("canvas")?
.dyn_into::<HtmlCanvasElement>()?;
canvas.set_width(width);
canvas.set_height(height);
Ok(canvas)
}

View file

@ -6,17 +6,39 @@
<style>
body {
font-family: system-ui, sans-serif;
background-color: Cornsilk;
margin: 0;
padding: 0;
overflow: hidden;
}
#fractal-canvas {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
}
.content-overlay {
position: relative;
z-index: 1;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
header h2 {
font-weight: normal;
font-family: "Gill Sans", sans-serif;
text-align: center;
}
body {
margin: 0;
padding: 0;
color: #fff;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
background: rgba(0, 0, 0, 0.3);
padding: 0.5rem 1.5rem;
border-radius: 8px;
}
nav.directory {
@ -31,25 +53,72 @@
nav.directory dt a {
text-decoration: none;
color: #333;
color: #fff;
padding: 0.5rem 1rem;
display: inline-block;
background: rgba(0, 0, 0, 0.3);
border-radius: 4px;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
transition: background 0.2s;
}
nav.directory dt a:hover {
background: rgba(0, 0, 0, 0.5);
}
</style>
</head>
<body>
<header>
<h2>Zachery Aaron Shores-Chmielewski</h2>
</header>
<canvas id="fractal-canvas"></canvas>
<nav class="directory">
<nav>
<dl>
<dt><a href="./who">Who</a></dt>
<!-- <dt><a href="#">Writing</a></dt> -->
<dt><a href="./contact">Send me a message</a></dt>
</dl>
<div class="content-overlay">
<header>
<h2>Zachery Aaron Shores-Chmielewski</h2>
</header>
<nav class="directory">
<nav>
<dl>
<dt><a href="./who">Who</a></dt>
<dt><a href="./contact">Send me a message</a></dt>
</dl>
</nav>
</nav>
</nav>
</div>
<script src="/routes/root/pkg/fractal_engine.js"></script>
<script>
(async function() {
await wasm_bindgen('/routes/root/pkg/fractal_engine_bg.wasm');
const canvas = document.getElementById('fractal-canvas');
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
let startTime = performance.now();
const animationSpeed = 0.0001;
const scale = 4; // Render at 1/4 resolution for performance
function animate() {
const elapsed = performance.now() - startTime;
const time = elapsed * animationSpeed;
try {
wasm_bindgen.render_fractal_animated('fractal-canvas', time, scale);
} catch (e) {
console.error('Fractal render error:', e);
}
requestAnimationFrame(animate);
}
animate();
})();
</script>
</body>
</html>